From 9460e6d5b0e884955f4142be56ecb057058d9f62 Mon Sep 17 00:00:00 2001 From: Ian Unruh Date: Mon, 7 Mar 2022 20:42:05 -0700 Subject: [PATCH] nova: Add compute PKI --- api/v1beta1/novacomputenode_types.go | 4 ++ ...openstack.ospk8s.com_novacomputenodes.yaml | 5 ++ controllers/nova_controller.go | 8 +++ controllers/novacomputenode_controller.go | 25 ++++++++ pkg/nova/compute.go | 39 +++++++++++++ pkg/nova/computeset/reconcile.go | 1 + pkg/nova/pki.go | 57 +++++++++++++++++++ pkg/pki/certificate.go | 4 ++ templates/nova/compute-node-setup.sh | 2 + templates/pki/certificate.yaml | 2 +- 10 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 pkg/nova/pki.go create mode 100644 templates/nova/compute-node-setup.sh diff --git a/api/v1beta1/novacomputenode_types.go b/api/v1beta1/novacomputenode_types.go index cdeed587..339bbe8c 100644 --- a/api/v1beta1/novacomputenode_types.go +++ b/api/v1beta1/novacomputenode_types.go @@ -25,6 +25,8 @@ type NovaComputeNodeSpec struct { Node string `json:"node"` Cell string `json:"cell"` + + Set string `json:"set"` } // NovaComputeNodeStatus defines the observed state of NovaComputeNode @@ -32,6 +34,8 @@ type NovaComputeNodeStatus struct { Conditions []metav1.Condition `json:"conditions"` Hypervisor *NovaHypervisorStatus `json:"hypervisor,omitempty"` + + SetupJobHash string `json:"setupJobHash,omitempty"` } type NovaHypervisorStatus struct { diff --git a/config/crd/bases/openstack.ospk8s.com_novacomputenodes.yaml b/config/crd/bases/openstack.ospk8s.com_novacomputenodes.yaml index c4fef9b7..3b4f436b 100644 --- a/config/crd/bases/openstack.ospk8s.com_novacomputenodes.yaml +++ b/config/crd/bases/openstack.ospk8s.com_novacomputenodes.yaml @@ -47,9 +47,12 @@ spec: type: string node: type: string + set: + type: string required: - cell - node + - set type: object status: description: NovaComputeNodeStatus defines the observed state of NovaComputeNode @@ -148,6 +151,8 @@ spec: - taskCount - up type: object + setupJobHash: + type: string required: - conditions type: object diff --git a/controllers/nova_controller.go b/controllers/nova_controller.go index 8bd255fd..d2403ab6 100644 --- a/controllers/nova_controller.go +++ b/controllers/nova_controller.go @@ -76,6 +76,14 @@ func (r *NovaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return ctrl.Result{}, err } + pkiResources := nova.PKIResources(instance) + for _, resource := range pkiResources { + controllerutil.SetControllerReference(instance, resource, r.Scheme) + if err := template.EnsureResource(ctx, r.Client, resource, log); err != nil { + return ctrl.Result{}, err + } + } + deps := template.NewConditionWaiter(log) databases := []*openstackv1beta1.MariaDBDatabase{ diff --git a/controllers/novacomputenode_controller.go b/controllers/novacomputenode_controller.go index f9a11a40..51ff793d 100644 --- a/controllers/novacomputenode_controller.go +++ b/controllers/novacomputenode_controller.go @@ -29,10 +29,12 @@ import ( "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" openstackv1beta1 "github.com/ianunruh/openstack-operator/api/v1beta1" "github.com/ianunruh/openstack-operator/pkg/nova" "github.com/ianunruh/openstack-operator/pkg/nova/computenode" + "github.com/ianunruh/openstack-operator/pkg/template" ) // NovaComputeNodeReconciler reconciles a NovaComputeNode object @@ -61,6 +63,16 @@ func (r *NovaComputeNodeReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, err } + set := &openstackv1beta1.NovaComputeSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Spec.Set, + Namespace: instance.Namespace, + }, + } + if err := r.Client.Get(ctx, client.ObjectKeyFromObject(set), set); err != nil { + return ctrl.Result{}, err + } + if computenode.ReadyCondition(instance) == nil { reporter.Pending(instance, nil, "ComputeNodePending", "Waiting for compute node to be reconciled") if err := r.Client.Status().Update(ctx, instance); err != nil { @@ -68,6 +80,19 @@ func (r *NovaComputeNodeReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } + certificate := nova.ComputeCertificate(instance) + controllerutil.SetControllerReference(instance, certificate, r.Scheme) + if err := template.EnsureResource(ctx, r.Client, certificate, log); err != nil { + return ctrl.Result{}, err + } + + jobs := template.NewJobRunner(ctx, r.Client, log) + jobs.Add(&instance.Status.SetupJobHash, + nova.ComputeNodeSetupJob(instance, set.Spec.Image)) + if result, err := jobs.Run(instance); err != nil || !result.IsZero() { + return result, err + } + // TODO handle this user not existing on deletion, and remove the finalizer anyway svcUser := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/nova/compute.go b/pkg/nova/compute.go index 0ede71b6..aac2a26e 100644 --- a/pkg/nova/compute.go +++ b/pkg/nova/compute.go @@ -4,6 +4,7 @@ import ( "strconv" appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" openstackv1beta1 "github.com/ianunruh/openstack-operator/api/v1beta1" @@ -119,3 +120,41 @@ func ComputeDaemonSet(instance *openstackv1beta1.NovaComputeSet, env []corev1.En return ds } + +func ComputeNodeSetupJob(instance *openstackv1beta1.NovaComputeNode, containerImage string) *batchv1.Job { + labels := template.AppLabels(instance.Name, AppLabel) + + defaultMode := int32(0400) + + // TODO resources + job := template.GenericJob(template.Component{ + Namespace: instance.Namespace, + Labels: labels, + Containers: []corev1.Container{ + { + Name: "setup", + Image: containerImage, + Command: []string{ + "bash", + "-c", + template.MustReadFile(AppLabel, "compute-node-setup.sh"), + }, + VolumeMounts: []corev1.VolumeMount{ + template.VolumeMount("etc-nova-tls", "/etc/nova/tls"), + template.VolumeMount("nova-tls", "/var/run/secrets/nova-tls"), + }, + }, + }, + NodeSelector: map[string]string{ + "kubernetes.io/hostname": instance.Spec.Node, + }, + Volumes: []corev1.Volume{ + template.HostPathVolume("etc-nova-tls", "/etc/nova/tls"), + template.SecretVolume("nova-tls", instance.Name, &defaultMode), + }, + }) + + job.Name = template.Combine(instance.Name, "setup") + + return job +} diff --git a/pkg/nova/computeset/reconcile.go b/pkg/nova/computeset/reconcile.go index fbbc05f8..b51d342e 100644 --- a/pkg/nova/computeset/reconcile.go +++ b/pkg/nova/computeset/reconcile.go @@ -124,6 +124,7 @@ func newComputeNode(instance *openstackv1beta1.NovaComputeSet, node corev1.Node) Spec: openstackv1beta1.NovaComputeNodeSpec{ Node: node.Name, Cell: instance.Spec.Cell, + Set: instance.Name, }, } } diff --git a/pkg/nova/pki.go b/pkg/nova/pki.go new file mode 100644 index 00000000..c6d047ef --- /dev/null +++ b/pkg/nova/pki.go @@ -0,0 +1,57 @@ +package nova + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + openstackv1beta1 "github.com/ianunruh/openstack-operator/api/v1beta1" + "github.com/ianunruh/openstack-operator/pkg/pki" + "github.com/ianunruh/openstack-operator/pkg/template" +) + +func PKIResources(instance *openstackv1beta1.Nova) []*unstructured.Unstructured { + return []*unstructured.Unstructured{ + CARootCertificate(instance), + CAIssuer(instance), + SelfSignedIssuer(instance), + } +} + +func ComputeCertificate(instance *openstackv1beta1.NovaComputeNode) *unstructured.Unstructured { + return pki.Certificate(pki.CertificateParams{ + Name: instance.Name, + Namespace: instance.Namespace, + CommonName: instance.Spec.Node, + SecretName: instance.Name, + // TODO make configurable + IssuerName: "nova-ca", + Usages: []string{ + "digital signature", + "key encipherment", + }, + }) +} + +func CARootCertificate(instance *openstackv1beta1.Nova) *unstructured.Unstructured { + return pki.Certificate(pki.CertificateParams{ + Name: template.Combine(instance.Name, "ca-root"), + Namespace: instance.Namespace, + SecretName: template.Combine(instance.Name, "ca-root"), + IssuerName: template.Combine(instance.Name, "self-signed"), + IsCA: true, + }) +} + +func CAIssuer(instance *openstackv1beta1.Nova) *unstructured.Unstructured { + return pki.CAIssuer(pki.IssuerParams{ + Name: template.Combine(instance.Name, "ca"), + Namespace: instance.Namespace, + SecretName: template.Combine(instance.Name, "ca-root"), + }) +} + +func SelfSignedIssuer(instance *openstackv1beta1.Nova) *unstructured.Unstructured { + return pki.SelfSignedIssuer(pki.IssuerParams{ + Name: template.Combine(instance.Name, "self-signed"), + Namespace: instance.Namespace, + }) +} diff --git a/pkg/pki/certificate.go b/pkg/pki/certificate.go index 41be125f..f4742875 100644 --- a/pkg/pki/certificate.go +++ b/pkg/pki/certificate.go @@ -9,6 +9,7 @@ import ( type CertificateParams struct { Name string Namespace string + CommonName string SecretName string IssuerName string IsCA bool @@ -16,6 +17,9 @@ type CertificateParams struct { } func Certificate(params CertificateParams) *unstructured.Unstructured { + if params.CommonName == "" { + params.CommonName = params.Name + } manifest := template.MustRenderFile("pki", "certificate.yaml", params) return template.MustDecodeManifest(manifest) } diff --git a/templates/nova/compute-node-setup.sh b/templates/nova/compute-node-setup.sh new file mode 100644 index 00000000..f6254883 --- /dev/null +++ b/templates/nova/compute-node-setup.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cp /var/run/secrets/nova-tls/* /etc/nova/tls/ diff --git a/templates/pki/certificate.yaml b/templates/pki/certificate.yaml index 21beffc8..19365fe2 100644 --- a/templates/pki/certificate.yaml +++ b/templates/pki/certificate.yaml @@ -6,7 +6,7 @@ metadata: namespace: {{ .Namespace }} spec: isCA: {{ .IsCA }} - commonName: {{ .Name }} + commonName: {{ .CommonName }} secretName: {{ .SecretName }} usages: [{{ StringsJoin .Usages ", " }}] privateKey: