Skip to content

Commit

Permalink
feat: create a Certificate object for each VM
Browse files Browse the repository at this point in the history
  • Loading branch information
conradludgate committed Jan 21, 2025
1 parent cace4c8 commit 7f8372b
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 5 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/alessio/shellescape v1.4.1
github.com/aws/aws-sdk-go-v2/config v1.27.12
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2
github.com/cert-manager/cert-manager v1.12.0
github.com/cilium/cilium v1.12.14
github.com/containerd/cgroups/v3 v3.0.1
github.com/containernetworking/cni v1.1.1
Expand Down Expand Up @@ -154,7 +155,6 @@ require (
github.com/klauspost/compress v1.15.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
Expand Down Expand Up @@ -220,6 +220,7 @@ require (
k8s.io/mount-utils v0.0.0 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
sigs.k8s.io/gateway-api v0.6.2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
Expand Down
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cert-manager/cert-manager v1.12.0 h1:CWIZeWop7RwFCIKgSzsxFFGcI2nvudkOICBMDY7SKuI=
github.com/cert-manager/cert-manager v1.12.0/go.mod h1:vRRQLs67q9PN/3SILHpiLbzuG63c4I0+q6pbppEWChs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down Expand Up @@ -276,8 +278,8 @@ github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHT
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
Expand Down Expand Up @@ -474,7 +476,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand Down Expand Up @@ -589,6 +590,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2S
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y=
sigs.k8s.io/controller-runtime v0.17.6 h1:12IXsozEsIXWAMRpgRlYS1jjAHQXHtWEOMdULh3DbEw=
sigs.k8s.io/controller-runtime v0.17.6/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY=
sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA=
sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
Expand Down
4 changes: 4 additions & 0 deletions neonvm-controller/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
Expand Down Expand Up @@ -203,6 +204,9 @@ func main() {
FailingRefreshInterval: failingRefreshInterval,
AtMostOnePod: atMostOnePod,
DefaultCPUScalingMode: defaultCpuScalingMode,
CertificateIssuer: "neon-ca-issuer",
CertificateDuration: 86400 * time.Second,
CertificateRenewal: 3600 * time.Second,
}

vmReconciler := &controllers.VMReconciler{
Expand Down
2 changes: 2 additions & 0 deletions neonvm/apis/neonvm/v1/virtualmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,8 @@ type VirtualMachineStatus struct {
MemoryProvider *MemoryProvider `json:"memoryProvider,omitempty"`
// +optional
SSHSecretName string `json:"sshSecretName,omitempty"`
// +optional
CertificateName string `json:"certificateName,omitempty"`

// CurrentRevision is updated with Spec.TargetRevision's value once
// the changes are propagated to the VM.
Expand Down
9 changes: 9 additions & 0 deletions pkg/neonvm/controllers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,13 @@ type ReconcilerConfig struct {
AtMostOnePod bool
// DefaultCPUScalingMode is the default CPU scaling mode that will be used for VMs with empty spec.cpuScalingMode
DefaultCPUScalingMode vmv1.CpuScalingMode

// The name of the ClusterIssuer that will issue TLS certificates for the VM.
CertificateIssuer string

// Duration that certificates are valid for
CertificateDuration time.Duration

// How long before expiration should certificates be renewed.
CertificateRenewal time.Duration
}
97 changes: 96 additions & 1 deletion pkg/neonvm/controllers/vm_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"strconv"
"time"

certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
nadapiv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
"github.com/samber/lo"
"golang.org/x/crypto/ssh"
Expand Down Expand Up @@ -408,11 +410,40 @@ func (r *VMReconciler) doReconcile(ctx context.Context, vm *vmv1.VirtualMachine)
}
}

// Check if the TLS certificate already exists, if not create a new one
certificate := &certv1.Certificate{}
err := r.Get(ctx, types.NamespacedName{Name: vm.Name, Namespace: vm.Namespace}, certificate)
if err != nil && apierrors.IsNotFound(err) {
// Define a new pod
certificate, err := r.certForVirtualMachine(vm)
if err != nil {
log.Error(err, "Failed to define new Certificate resource for VirtualMachine")
return err
}

log.Info("Creating a new Certificate", "Certificate.Namespace", certificate.Namespace, "Certificate.Name", certificate.Name)
if err = r.Create(ctx, certificate); err != nil {
log.Error(err, "Failed to create new Certificate", "Certificate.Namespace", certificate.Namespace, "Certificate.Name", certificate.Name)
return err
}
log.Info("Runner Certificate was created", "Certificate.Namespace", certificate.Namespace, "Certificate.Name", certificate.Name)

msg := fmt.Sprintf("VirtualMachine %s created, Certificate %s", vm.Name, certificate.Name)
r.Recorder.Event(vm, "Normal", "Created", msg)
if !vm.HasRestarted() {
d := certificate.CreationTimestamp.Time.Sub(vm.CreationTimestamp.Time)
r.Metrics.vmCreationToRunnerCreationTime.Observe(d.Seconds())
}
} else if err != nil {
log.Error(err, "Failed to get vm-runner Certificate")
return err
}

memoryProvider := *vm.Status.MemoryProvider

// Check if the runner pod already exists, if not create a new one
vmRunner := &corev1.Pod{}
err := r.Get(ctx, types.NamespacedName{Name: vm.Status.PodName, Namespace: vm.Namespace}, vmRunner)
err = r.Get(ctx, types.NamespacedName{Name: vm.Status.PodName, Namespace: vm.Namespace}, vmRunner)
if err != nil && apierrors.IsNotFound(err) {
var sshSecret *corev1.Secret
if enableSSH {
Expand Down Expand Up @@ -1175,6 +1206,24 @@ func sshSecretSpec(vm *vmv1.VirtualMachine) (*corev1.Secret, error) {
return secret, nil
}

// podForVirtualMachine returns a VirtualMachine Pod object
func (r *VMReconciler) certForVirtualMachine(
vm *vmv1.VirtualMachine,
) (*certv1.Certificate, error) {
cert, err := certSpec(vm, r.Config)
if err != nil {
return nil, err
}

// Set the ownerRef for the Certificate
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/
if err := ctrl.SetControllerReference(vm, cert, r.Scheme); err != nil {
return nil, err
}

return cert, nil
}

// labelsForVirtualMachine returns the labels for selecting the resources
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
func labelsForVirtualMachine(vm *vmv1.VirtualMachine, runnerVersion *api.RunnerProtoVersion) map[string]string {
Expand Down Expand Up @@ -1640,6 +1689,52 @@ func podSpec(
return pod, nil
}

func certSpec(
vm *vmv1.VirtualMachine,
config *ReconcilerConfig,
) (*certv1.Certificate, error) {
runnerVersion := api.RunnerProtoV1
labels := labelsForVirtualMachine(vm, &runnerVersion)
annotations := annotationsForVirtualMachine(vm)

commonName := fmt.Sprintf("%s.%s.svc.cluster.local", vm.Name, vm.Namespace)

issuer := cmmeta.ObjectReference{
Name: config.CertificateIssuer,
Kind: "ClusterIssuer",
}

secretName := fmt.Sprintf("tls-%s", vm.Name)

certSpec := certv1.CertificateSpec{
CommonName: commonName,
DNSNames: []string{commonName},
IssuerRef: issuer,
SecretName: secretName,
PrivateKey: &certv1.CertificatePrivateKey{
// todo: can we support Ed25519?
Algorithm: certv1.ECDSAKeyAlgorithm,
Encoding: certv1.PKCS8,
RotationPolicy: certv1.RotationPolicyAlways,
},
Usages: []certv1.KeyUsage{
certv1.UsageServerAuth,
},
Duration: &metav1.Duration{Duration: config.CertificateDuration},
RenewBefore: &metav1.Duration{Duration: config.CertificateRenewal},
}

return &certv1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: vm.Name,
Namespace: vm.Namespace,
Labels: labels,
Annotations: annotations,
},
Spec: certSpec,
}, nil
}

// SetupWithManager sets up the controller with the Manager.
// Note that the Runner Pod will be also watched in order to ensure its
// desirable state on the cluster
Expand Down
2 changes: 2 additions & 0 deletions pkg/neonvm/controllers/vm_controller_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -96,6 +97,7 @@ func newTestParams(t *testing.T) *testParams {
scheme := runtime.NewScheme()
scheme.AddKnownTypes(vmv1.SchemeGroupVersion, &vmv1.VirtualMachine{})
scheme.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Pod{})
scheme.AddKnownTypes(certv1.SchemeGroupVersion, &certv1.Certificate{})

params := &testParams{
t: t,
Expand Down

0 comments on commit 7f8372b

Please sign in to comment.