diff --git a/build/liqo-test/Dockerfile b/build/liqo-test/Dockerfile index f1b34ade0c..ed5561c7a5 100644 --- a/build/liqo-test/Dockerfile +++ b/build/liqo-test/Dockerfile @@ -1,14 +1,16 @@ FROM golang:1.16 as builder -ENV PATH /go/bin:/usr/local/go/bin:/usr/local/kubebuilder/bin:$PATH +ENV PATH /go/bin:/usr/local/go/bin:/opt/kubebuilder/testbin:$PATH ENV GOPATH /go +ENV K8S_VERSION=1.19.2 WORKDIR /go/src/github.com/liqotech/liqo COPY go.mod ./go.mod COPY go.sum ./go.sum RUN go mod download # Install kubebuilder -RUN curl -sL https://go.kubebuilder.io/dl/2.3.0/$(go env GOOS)/$(go env GOARCH) | tar -xz -C /tmp/ -RUN mv /tmp/kubebuilder_2.3.0_$(go env GOOS)_$(go env GOARCH) /usr/local/kubebuilder +RUN curl --fail -sSLo envtest-bins.tar.gz "https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-${K8S_VERSION}-$(go env GOOS)-$(go env GOARCH).tar.gz" +RUN mkdir /usr/local/kubebuilder/ +RUN tar -C /usr/local/kubebuilder/ --strip-components=1 -zvxf envtest-bins.tar.gz # Install goimports RUN GO111MODULE="on" go get -u github.com/ory/go-acc diff --git a/cmd/init-virtual-kubelet/main.go b/cmd/init-virtual-kubelet/main.go index bffebd4b07..bcfe3746e4 100644 --- a/cmd/init-virtual-kubelet/main.go +++ b/cmd/init-virtual-kubelet/main.go @@ -2,11 +2,10 @@ package main import ( "context" + "flag" "os" "path/filepath" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -18,7 +17,10 @@ import ( func main() { var config *rest.Config + var distribution string klog.Info("Loading client config") + flag.StringVar(&distribution, "k8s-distribution", "kubernetes", "determine the provider to adapt csr generation") + ctx := context.Background() kubeconfigPath, ok := os.LookupEnv("KUBECONFIG") if !ok { @@ -42,11 +44,11 @@ func main() { } // Generate Key and CSR files in PEM format - if err := createCSRResource(name, client, vk.CsrLocation, vk.KeyLocation); err != nil { + if err := csr.CreateCSRResource(ctx, name, client, vk.CsrLocation, vk.KeyLocation, distribution); err != nil { klog.Fatalf("Unable to create CSR: %s", err) } - cert, err := csr.WaitForApproval(client, name) + cert, err := csr.WaitForApproval(ctx, client, name) if err != nil { klog.Fatalf("Unable to get certificate: %s", err) } @@ -55,29 +57,3 @@ func main() { os.Exit(1) } } - -func createCSRResource(name string, client kubernetes.Interface, CsrLocation string, KeyLocation string) error { - csrPem, keyPem, err := csr.GenerateVKCertificateBundle(name) - if err != nil { - return err - } - - if err := utils.WriteFile(CsrLocation, csrPem); err != nil { - return err - } - - if err := utils.WriteFile(KeyLocation, keyPem); err != nil { - return err - } - - // Generate and create CSR resource - csrResource := csr.GenerateVKCSR(name, csrPem) - _, err = client.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), csrResource, metav1.CreateOptions{}) - if errors.IsAlreadyExists(err) { - klog.Infof("CSR already exists: %s", err) - } else if err != nil { - klog.Errorf("Unable to create CSR: %s", err) - return err - } - return nil -} diff --git a/pkg/identityManager/identityManager_test.go b/pkg/identityManager/identityManager_test.go index 19bbec4803..9beed4e359 100644 --- a/pkg/identityManager/identityManager_test.go +++ b/pkg/identityManager/identityManager_test.go @@ -182,14 +182,14 @@ var _ = Describe("IdentityManager", func() { certificate, err := identityManager.ApproveSigningRequest(remoteClusterID, base64.StdEncoding.EncodeToString(csrBytes)) Expect(err).To(BeNil()) Expect(certificate).NotTo(BeNil()) - Expect(certificate).To(Equal([]byte("test"))) + Expect(certificate).To(Equal([]byte(idManTest.FakeCRT))) }) It("Retrieve Remote Certificate", func() { certificate, err := identityManager.GetRemoteCertificate(remoteClusterID, base64.StdEncoding.EncodeToString(csrBytes)) Expect(err).To(BeNil()) Expect(certificate).NotTo(BeNil()) - Expect(certificate).To(Equal([]byte("test"))) + Expect(certificate).To(Equal([]byte(idManTest.FakeCRT))) }) It("Retrieve Remote Certificate wrong clusterid", func() { diff --git a/pkg/identityManager/signingRequest.go b/pkg/identityManager/signingRequest.go index f6cff02eeb..8f7f67ee09 100644 --- a/pkg/identityManager/signingRequest.go +++ b/pkg/identityManager/signingRequest.go @@ -8,7 +8,7 @@ import ( "strings" "time" - certv1beta1 "k8s.io/api/certificates/v1beta1" + certv1 "k8s.io/api/certificates/v1" v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,9 +18,8 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" - "k8s.io/utils/pointer" - certificateSigningRequest2 "github.com/liqotech/liqo/pkg/utils/certificateSigningRequest" + certificateSigningRequest "github.com/liqotech/liqo/pkg/vkMachinery/csr" ) // random package initialization. @@ -30,7 +29,7 @@ func init() { // GetRemoteCertificate retrieves a certificate issued in the past, // given the clusterid and the signingRequest. -func (certManager *certificateIdentityManager) GetRemoteCertificate(clusterID string, signingRequest string) (certificate []byte, err error) { +func (certManager *certificateIdentityManager) GetRemoteCertificate(clusterID, signingRequest string) (certificate []byte, err error) { namespace, err := certManager.namespaceManager.GetNamespace(clusterID) if err != nil { klog.Error(err) @@ -80,7 +79,7 @@ func (certManager *certificateIdentityManager) GetRemoteCertificate(clusterID st // ApproveSigningRequest approves a remote CertificateSigningRequest. // It creates a CertificateSigningRequest CR to be issued by the local cluster, and approves it. // This function will wait (with a timeout) for an available certificate before returning. -func (certManager *certificateIdentityManager) ApproveSigningRequest(clusterID string, signingRequest string) (certificate []byte, err error) { +func (certManager *certificateIdentityManager) ApproveSigningRequest(clusterID, signingRequest string) (certificate []byte, err error) { rnd := fmt.Sprintf("%v", rand.Int63()) signingBytes, err := base64.StdEncoding.DecodeString(signingRequest) @@ -89,8 +88,7 @@ func (certManager *certificateIdentityManager) ApproveSigningRequest(clusterID s return nil, err } - // TODO: move client-go to a newer version to use certificates/v1 - cert := &certv1beta1.CertificateSigningRequest{ + cert := &certv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: strings.Join([]string{identitySecretRoot, ""}, "-"), Labels: map[string]string{ @@ -98,28 +96,29 @@ func (certManager *certificateIdentityManager) ApproveSigningRequest(clusterID s randomIDLabel: rnd, }, }, - Spec: certv1beta1.CertificateSigningRequestSpec{ + Spec: certv1.CertificateSigningRequestSpec{ Groups: []string{ "system:authenticated", }, - SignerName: pointer.StringPtr(certv1beta1.KubeAPIServerClientSignerName), + SignerName: certv1.KubeAPIServerClientSignerName, Request: signingBytes, - Usages: []certv1beta1.KeyUsage{ - certv1beta1.UsageDigitalSignature, - certv1beta1.UsageKeyEncipherment, - certv1beta1.UsageClientAuth, + Usages: []certv1.KeyUsage{ + certv1.UsageDigitalSignature, + certv1.UsageKeyEncipherment, + certv1.UsageClientAuth, }, }, } - cert, err = certManager.client.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err = certManager.client.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), cert, metav1.CreateOptions{}) if err != nil { klog.Error(err) return nil, err } // approve the CertificateSigningRequest - if err = certificateSigningRequest2.ApproveCSR(certManager.client, cert, "IdentityManagerApproval", "This CSR was approved by Liqo Identity Manager"); err != nil { + if err = certificateSigningRequest.ApproveCSR(certManager.client, cert, "IdentityManagerApproval", + "This CSR was approved by Liqo Identity Manager"); err != nil { klog.Error(err) return nil, err } @@ -141,18 +140,18 @@ func (certManager *certificateIdentityManager) ApproveSigningRequest(clusterID s // getCertificate retrieves the certificate given the CertificateSigningRequest and its randomID. // If the certificate is not ready yet, it will wait for it (with a timeout). -func (certManager *certificateIdentityManager) getCertificate(csr *certv1beta1.CertificateSigningRequest, randomID string) ([]byte, error) { +func (certManager *certificateIdentityManager) getCertificate(csr *certv1.CertificateSigningRequest, randomID string) ([]byte, error) { var certificate []byte // define a function that will check if a generic object is a CSR with a issued certificate checkCertificate := func(obj interface{}) bool { - certificateSigningRequest, ok := obj.(*certv1beta1.CertificateSigningRequest) + certificateSigningRequest, ok := obj.(*certv1.CertificateSigningRequest) if !ok { klog.Errorf("this object is not a CertificateSigningRequest: %v", obj) return false } - res := (certificateSigningRequest.Status.Certificate != nil && len(certificateSigningRequest.Status.Certificate) > 0) + res := certificateSigningRequest.Status.Certificate != nil && len(certificateSigningRequest.Status.Certificate) > 0 if res { certificate = certificateSigningRequest.Status.Certificate } @@ -173,13 +172,13 @@ func (certManager *certificateIdentityManager) getCertificate(csr *certv1beta1.C informer := cache.NewSharedIndexInformer(&cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { options.LabelSelector = labelSelector.String() - return certManager.client.CertificatesV1beta1().CertificateSigningRequests().List(context.TODO(), options) + return certManager.client.CertificatesV1().CertificateSigningRequests().List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { options.LabelSelector = labelSelector.String() - return certManager.client.CertificatesV1beta1().CertificateSigningRequests().Watch(context.TODO(), options) + return certManager.client.CertificatesV1().CertificateSigningRequests().Watch(context.TODO(), options) }, - }, &certv1beta1.CertificateSigningRequest{}, 0, cache.Indexers{}) + }, &certv1.CertificateSigningRequest{}, 0, cache.Indexers{}) stopChan := make(chan struct{}) informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -211,7 +210,7 @@ func (certManager *certificateIdentityManager) getCertificate(csr *certv1beta1.C } // storeRemoteCertificate stores the issued certificate in a Secret in the TenantControlNamespace. -func (certManager *certificateIdentityManager) storeRemoteCertificate(clusterID string, signingRequest []byte, certificate []byte) (*v1.Secret, error) { +func (certManager *certificateIdentityManager) storeRemoteCertificate(clusterID string, signingRequest, certificate []byte) (*v1.Secret, error) { namespace, err := certManager.namespaceManager.GetNamespace(clusterID) if err != nil { klog.Error(err) diff --git a/pkg/identityManager/testUtils/csrApprover.go b/pkg/identityManager/testUtils/csrApprover.go index 6fba1f703c..5af7cd4e41 100644 --- a/pkg/identityManager/testUtils/csrApprover.go +++ b/pkg/identityManager/testUtils/csrApprover.go @@ -4,7 +4,7 @@ import ( "context" "os" - certv1beta1 "k8s.io/api/certificates/v1beta1" + certv1 "k8s.io/api/certificates/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" @@ -13,29 +13,50 @@ import ( "k8s.io/klog" ) +// FakeCRT is the fake CRT returned by the TestApprover as valid CRT. +var FakeCRT = ` +-----BEGIN CERTIFICATE----- +MIIBvzCCAWWgAwIBAgIRAMd7Mz3fPrLm1aFUn02lLHowCgYIKoZIzj0EAwIwIzEh +MB8GA1UEAwwYazNzLWNsaWVudC1jYUAxNjE2NDMxOTU2MB4XDTIxMDQxOTIxNTMz +MFoXDTIyMDQxOTIxNTMzMFowMjEVMBMGA1UEChMMc3lzdGVtOm5vZGVzMRkwFwYD +VQQDExBzeXN0ZW06bm9kZTp0ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +Xd9aZm6nftepZpUwof9RSUZqZDgu7dplIiDt8nnhO5Bquy2jn7/AVx20xb0Xz0d2 +XLn3nn5M+lR2p3NlZmqWHaNrMGkwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG +CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU/fZa5enijRDB25DF +NT1/vPUy/hMwEwYDVR0RBAwwCoIIRE5TOnRlc3QwCgYIKoZIzj0EAwIDSAAwRQIg +b3JL5+Q3zgwFrciwfdgtrKv8MudlA0nu6EDQO7eaJbwCIQDegFyC4tjGPp/5JKqQ +kovW9X7Ook/tTW0HyX6D6HRciA== +-----END CERTIFICATE----- +` + +// StartTestApprover mocks the CSRApprover. +// When a CSR is approved, it injects a fake certificate to fill the .status.Certificate field. func StartTestApprover(client kubernetes.Interface, stopChan <-chan struct{}) { // we need an informer to fill the certificate field, since no api server is running informer := cache.NewSharedIndexInformer(&cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return client.CertificatesV1beta1().CertificateSigningRequests().List(context.TODO(), options) + return client.CertificatesV1().CertificateSigningRequests().List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return client.CertificatesV1beta1().CertificateSigningRequests().Watch(context.TODO(), options) + return client.CertificatesV1().CertificateSigningRequests().Watch(context.TODO(), options) }, - }, &certv1beta1.CertificateSigningRequest{}, 0, cache.Indexers{}) + }, &certv1.CertificateSigningRequest{}, 0, cache.Indexers{}) informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ UpdateFunc: func(oldObj interface{}, newObj interface{}) { - csr, ok := newObj.(*certv1beta1.CertificateSigningRequest) + csr, ok := newObj.(*certv1.CertificateSigningRequest) if !ok { klog.Info("not a csr") os.Exit(1) } if csr.Status.Certificate == nil { - csr.Status.Certificate = []byte("test") - _, _ = client.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus( + csr.Status.Certificate = []byte(FakeCRT) + _, err := client.CertificatesV1().CertificateSigningRequests().UpdateStatus( context.TODO(), csr, metav1.UpdateOptions{}) + if err != nil { + klog.Error(err) + } } }, }) diff --git a/pkg/utils/certificateSigningRequest/approve.go b/pkg/utils/certificateSigningRequest/approve.go deleted file mode 100644 index 4dfc15e425..0000000000 --- a/pkg/utils/certificateSigningRequest/approve.go +++ /dev/null @@ -1,35 +0,0 @@ -package certificateSigningRequest - -import ( - "context" - - certificatesv1beta1 "k8s.io/api/certificates/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8s "k8s.io/client-go/kubernetes" -) - -// ApproveCSR approves the provided CertificateSigningRequest. -func ApproveCSR(clientSet k8s.Interface, csr *certificatesv1beta1.CertificateSigningRequest, reason string, message string) error { - // certificate already added to CSR - if csr.Status.Certificate != nil { - return nil - } - // Check if the certificate is already approved but the certificate is still not available - for _, b := range csr.Status.Conditions { - if b.Type == "Approved" { - return nil - } - } - // Approve - csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{ - Type: certificatesv1beta1.CertificateApproved, - Reason: reason, - Message: message, - LastUpdateTime: metav1.Now(), - }) - _, errApproval := clientSet.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{}) - if errApproval != nil { - return errApproval - } - return nil -} diff --git a/pkg/vkMachinery/const.go b/pkg/vkMachinery/const.go index 445b1212fc..c3ab766c5e 100644 --- a/pkg/vkMachinery/const.go +++ b/pkg/vkMachinery/const.go @@ -2,11 +2,19 @@ package vkMachinery import "path/filepath" +// VKCertsRootPath defines the path where VK certificates are stored. const VKCertsRootPath = "/etc/virtual-kubelet/certs" +// KeyLocation defines the path where the VK Key file is stored. var KeyLocation = filepath.Join(VKCertsRootPath, "server-key.pem") + +// CertLocation defines the path where the VK Certificate is stored. var CertLocation = filepath.Join(VKCertsRootPath, "server.crt") + +// CsrLocation defines the path where the VK CSR is stored. var CsrLocation = filepath.Join(VKCertsRootPath, "server.csr") + +// CsrLabels defines the labels attached to the CSR resource. var CsrLabels = map[string]string{ "virtual-kubelet.liqo.io/csr": "true", } diff --git a/pkg/vkMachinery/csr/approve.go b/pkg/vkMachinery/csr/approve.go index ef0314ea44..babec1bfee 100644 --- a/pkg/vkMachinery/csr/approve.go +++ b/pkg/vkMachinery/csr/approve.go @@ -1,11 +1,12 @@ package csr import ( + "context" + "fmt" "time" - certificateSigningRequest2 "github.com/liqotech/liqo/pkg/utils/certificateSigningRequest" - - certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + certificatesv1 "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" k8s "k8s.io/client-go/kubernetes" @@ -13,6 +14,35 @@ import ( "k8s.io/klog" ) +// ApproveCSR approves the provided CertificateSigningRequest. +func ApproveCSR(clientSet k8s.Interface, csr *certificatesv1.CertificateSigningRequest, reason, message string) error { + // certificate already added to CSR + if csr.Status.Certificate != nil { + return nil + } + // Check if the certificate is already approved but the certificate is still not available + for _, b := range csr.Status.Conditions { + if b.Type == "Approved" { + return nil + } + } + // Approve + csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Reason: reason, + Message: message, + LastUpdateTime: metav1.Now(), + Status: corev1.ConditionTrue, + }) + _, errApproval := clientSet.CertificatesV1().CertificateSigningRequests(). + UpdateApproval(context.TODO(), csr.Name, csr, metav1.UpdateOptions{}) + if errApproval != nil { + return errApproval + } + return nil +} + +// WatchCSR initializes informers to watch the creation of new CSRs issued for VirtualKubelet instances. func WatchCSR(clientset k8s.Interface, label string, resyncPeriod time.Duration) { stop := make(chan struct{}) lo := func(options *metav1.ListOptions) { @@ -25,12 +55,12 @@ func WatchCSR(clientset k8s.Interface, label string, resyncPeriod time.Duration) csrInformer := informer.Certificates().V1beta1().CertificateSigningRequests().Informer() csrInformer.AddEventHandler(&cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - csr, ok := obj.(*certificatesv1beta1.CertificateSigningRequest) + csr, ok := obj.(*certificatesv1.CertificateSigningRequest) if !ok { klog.Error("Unable to cast object") return } - err := certificateSigningRequest2.ApproveCSR(clientset, csr, "LiqoApproval", "This CSR was approved by Liqo Advertisement Operator") + err := ApproveCSR(clientset, csr, "LiqoApproval", "This CSR was approved by Liqo Advertisement Operator") if err != nil { klog.Error(err) } else { @@ -41,3 +71,25 @@ func WatchCSR(clientset k8s.Interface, label string, resyncPeriod time.Duration) go informer.Start(stop) } + +// WaitForApproval returns a CRT when available for a specific CSR resource. It timeouts after a while, if the CRT is not available. +func WaitForApproval(ctx context.Context, client k8s.Interface, name string) ([]byte, error) { + ticker := time.NewTicker(3 * time.Second) + timeout := time.NewTicker(1 * time.Minute) + for { + select { + case <-timeout.C: + return nil, fmt.Errorf("timeout elapsed waiting for the certificate %s to be forged", name) + case <-ticker.C: + crt, err := client.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + klog.Errorf("Unable to get CSR: %s", err) + } + if len(crt.Status.Certificate) > 0 { + klog.Infof("Certificate retrieved for CSR %s", crt.Name) + return crt.Status.Certificate, nil + } + klog.Warningf("Certificate not available for CSR %s", crt.Name) + } + } +} diff --git a/pkg/utils/certificateSigningRequest/approve_test.go b/pkg/vkMachinery/csr/approve_test.go similarity index 58% rename from pkg/utils/certificateSigningRequest/approve_test.go rename to pkg/vkMachinery/csr/approve_test.go index 89db1de563..d0bc2dd435 100644 --- a/pkg/utils/certificateSigningRequest/approve_test.go +++ b/pkg/vkMachinery/csr/approve_test.go @@ -1,18 +1,18 @@ -package certificateSigningRequest +package csr import ( "context" "testing" "github.com/stretchr/testify/assert" - certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + certificatesv1 "k8s.io/api/certificates/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" testclient "k8s.io/client-go/kubernetes/fake" ) func TestApproveSigningRequest(t *testing.T) { //setup - certificateToValidate := certificatesv1beta1.CertificateSigningRequest{ + certificateToValidate := certificatesv1.CertificateSigningRequest{ TypeMeta: v1.TypeMeta{}, ObjectMeta: v1.ObjectMeta{ Name: "to_validate", @@ -20,12 +20,12 @@ func TestApproveSigningRequest(t *testing.T) { "liqo.io/csr": "true", }, }, - Spec: certificatesv1beta1.CertificateSigningRequestSpec{}, - Status: certificatesv1beta1.CertificateSigningRequestStatus{}, + Spec: certificatesv1.CertificateSigningRequestSpec{}, + Status: certificatesv1.CertificateSigningRequestStatus{}, } c := testclient.NewSimpleClientset() - _, err := c.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), &certificateToValidate, v1.CreateOptions{}) + _, err := c.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), &certificateToValidate, v1.CreateOptions{}) if err != nil { t.Fail() } @@ -33,14 +33,14 @@ func TestApproveSigningRequest(t *testing.T) { if err != nil { t.Fail() } - cert, err := c.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), "to_validate", v1.GetOptions{}) + cert, err := c.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), "to_validate", v1.GetOptions{}) if err != nil { t.Fail() } assert.NotNil(t, cert) assert.NotEmpty(t, cert.Status.Conditions) conditions := cert.Status.Conditions - assert.Equal(t, conditions[0].Type, certificatesv1beta1.CertificateApproved) + assert.Equal(t, conditions[0].Type, certificatesv1.CertificateApproved) assert.Equal(t, conditions[0].Reason, "LiqoApproval") assert.Equal(t, conditions[0].Message, "This CSR was approved by Liqo Advertisement Operator") } diff --git a/pkg/vkMachinery/csr/const.go b/pkg/vkMachinery/csr/const.go new file mode 100644 index 0000000000..64c79414be --- /dev/null +++ b/pkg/vkMachinery/csr/const.go @@ -0,0 +1,5 @@ +package csr + +// SignerName constants to forge CSRs. +const kubeletServingSignerName = "kubernetes.io/kubelet-serving" +const kubeletAPIServingSignerName = "kubernetes.io/kube-apiserver-client-kubelet" diff --git a/pkg/vkMachinery/csr/creation.go b/pkg/vkMachinery/csr/creation.go new file mode 100644 index 0000000000..0b1685c1b8 --- /dev/null +++ b/pkg/vkMachinery/csr/creation.go @@ -0,0 +1,47 @@ +package csr + +import ( + "context" + + certificatesv1 "k8s.io/api/certificates/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + + "github.com/liqotech/liqo/pkg/utils" +) + +// CreateCSRResource creates a CSR Resource for a new Virtual Kubelet instance. +func CreateCSRResource(ctx context.Context, name string, client kubernetes.Interface, csrLocation, keyLocation, distribution string) error { + csrPem, keyPem, err := generateVKCertificateBundle(name) + var csrResource *certificatesv1.CertificateSigningRequest + if err != nil { + return err + } + + if err := utils.WriteFile(csrLocation, csrPem); err != nil { + return err + } + + if err := utils.WriteFile(keyLocation, keyPem); err != nil { + return err + } + + // Generate and create CSR resource + switch distribution { + case "kubernetes": + csrResource = GenerateVKCSR(name, csrPem, kubeletServingSignerName) + default: + csrResource = GenerateVKCSR(name, csrPem, kubeletAPIServingSignerName) + } + + _, err = client.CertificatesV1().CertificateSigningRequests().Create(ctx, csrResource, metav1.CreateOptions{}) + if errors.IsAlreadyExists(err) { + klog.Infof("CSR already exists: %s", err) + } else if err != nil { + klog.Errorf("Unable to create CSR: %s", err) + return err + } + return nil +} diff --git a/pkg/vkMachinery/csr/doc.go b/pkg/vkMachinery/csr/doc.go new file mode 100644 index 0000000000..834ee7d537 --- /dev/null +++ b/pkg/vkMachinery/csr/doc.go @@ -0,0 +1,2 @@ +// Package csr contains the logic required to generate, create and approve CSR for the Virtual Kubelet instances. +package csr diff --git a/pkg/vkMachinery/csr/generation.go b/pkg/vkMachinery/csr/generation.go index 0d05d80cac..10d32c0c47 100644 --- a/pkg/vkMachinery/csr/generation.go +++ b/pkg/vkMachinery/csr/generation.go @@ -1,7 +1,6 @@ package csr import ( - "context" "crypto/ecdsa" "crypto/elliptic" cryptorand "crypto/rand" @@ -9,29 +8,26 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "time" - certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + certificatesv1 "k8s.io/api/certificates/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/cert" "k8s.io/client-go/util/keyutil" - "k8s.io/klog/v2" vk "github.com/liqotech/liqo/pkg/vkMachinery" ) -// GenerateVKCertificateBundle generates respectively a key and a CSR in PEM format compliant +// generateVKCertificateBundle generates respectively a key and a CSR in PEM format compliant // with the K8s kubelet-serving signer taking a name as input. -func GenerateVKCertificateBundle(name string) (csrPEM []byte, keyPEM []byte, err error) { +func generateVKCertificateBundle(name string) (csrPEM, keyPEM []byte, err error) { // Generate a new private key. privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) if err != nil { - return nil, nil, fmt.Errorf("unable to generate a new private key: %v", err) + return nil, nil, fmt.Errorf("unable to generate a new private key: %w", err) } der, err := x509.MarshalECPrivateKey(privateKey) if err != nil { - return nil, nil, fmt.Errorf("unable to marshal the new key to DER: %v", err) + return nil, nil, fmt.Errorf("unable to marshal the new key to DER: %w", err) } keyPEM = pem.EncodeToMemory(&pem.Block{Type: keyutil.ECPrivateKeyBlockType, Bytes: der}) @@ -47,49 +43,25 @@ func GenerateVKCertificateBundle(name string) (csrPEM []byte, keyPEM []byte, err } csrPEM, err = cert.MakeCSRFromTemplate(privateKey, template) if err != nil { - return nil, nil, fmt.Errorf("unable to create a csr from the private key: %v", err) + return nil, nil, fmt.Errorf("unable to create a csr from the private key: %w", err) } return csrPEM, keyPEM, nil } -// WaitForApproval returns a CRT when available for a specific CSR resource. It timeouts after a while, if the CRT is not available. -func WaitForApproval(client k8s.Interface, name string) ([]byte, error) { - ticker := time.NewTicker(3 * time.Second) - timeout := time.NewTicker(1 * time.Minute) - for { - select { - case <-timeout.C: - return nil, fmt.Errorf("timeout elapsed waiting for the certificate %s to be forged", name) - case <-ticker.C: - crt, err := client.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - klog.Errorf("Unable to get CSR: %s", err) - } - if len(crt.Status.Certificate) > 0 { - klog.Infof("Certificate retrieved for CSR %s", crt.Name) - return crt.Status.Certificate, nil - } else { - klog.Warningf("Certificate not available for CSR %s", crt.Name) - } - } - } -} - -// GenerateCSR generate a certificates/v1beta1 CSR resource for a virtual-kubelet name and PEM CSR. -func GenerateVKCSR(name string, csr []byte) *certificatesv1beta1.CertificateSigningRequest { - signerName := "kubernetes.io/kubelet-serving" - return &certificatesv1beta1.CertificateSigningRequest{ +// GenerateVKCSR generate a certificates/v1 CSR resource for a virtual-kubelet name and PEM CSR. +func GenerateVKCSR(name string, csr []byte, signerName string) *certificatesv1.CertificateSigningRequest { + return &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: vk.CsrLabels, }, - Spec: certificatesv1beta1.CertificateSigningRequestSpec{ + Spec: certificatesv1.CertificateSigningRequestSpec{ Request: csr, - SignerName: &signerName, - Usages: []certificatesv1beta1.KeyUsage{ - certificatesv1beta1.UsageServerAuth, - certificatesv1beta1.UsageKeyEncipherment, - certificatesv1beta1.UsageDigitalSignature, + SignerName: signerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageServerAuth, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageDigitalSignature, }, }, } diff --git a/pkg/vkMachinery/forge/doc.go b/pkg/vkMachinery/forge/doc.go new file mode 100644 index 0000000000..c4887bbb59 --- /dev/null +++ b/pkg/vkMachinery/forge/doc.go @@ -0,0 +1,2 @@ +// Package forge contains the logic required to generate the virtual kubelet resources. +package forge