a73x

internal/server/syncsvc/hostcert.go

Ref:   Size: 2.3 KiB   History

package syncsvc

import (
	"fmt"
	"log/slog"

	"github.com/a73x/eitri/internal/pb"
	"golang.org/x/crypto/ssh"
)

// hostCertSigner signs a guest's public host key for the principal the control
// plane chose.
type hostCertSigner interface {
	SignHostCert(pub ssh.PublicKey, principal string) (certLine string, err error)
}

// SetHostCertSigner wires the guest host-cert signer. Called once at startup
// when the jump gate is enabled, alongside the other seams the API hands this
// service (SetConsoleDialer, SetAgentUpgrader).
func (s *Service) SetHostCertSigner(c hostCertSigner) { s.certs = c }

// signAndRecordHostCert certifies one guest's host key.
func (s *Service) signAndRecordHostCert(hostID, vmID, pubLine string) error {
	vm, err := s.st.GetVM(vmID)
	if err != nil {
		return fmt.Errorf("read vm: %w", err)
	}
	if vm.HostID != hostID {
		return fmt.Errorf("vm %s is not on host %s", vmID, hostID)
	}
	if vm.DeletedAt != nil {
		return fmt.Errorf("vm %s is tombstoned", vmID)
	}
	pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubLine))
	if err != nil {
		return fmt.Errorf("parse reported host key: %w", err)
	}
	cert, err := s.certs.SignHostCert(pub, vm.Tenant+"."+vm.Name)
	if err != nil {
		return fmt.Errorf("sign host cert: %w", err)
	}
	if err := s.st.RecordVMHostKey(vmID, hostID, pubLine, cert); err != nil {
		return fmt.Errorf("record host cert: %w", err)
	}
	s.hub.Poke(hostID)
	return nil
}

// certifyReportedHostKeys signs every newly-reported guest host key in one
// report. It runs ahead of — and outside — the status write-through loop,
// which only looks at VMs in phase ready or failed: a VM waiting for its
// certificate reports `creating`, and is exactly the VM that needs one.
//
// A failure is logged and dropped, like every other per-VM failure in a report:
// the key rides every later report too, so the next tick tries again.
func (s *Service) certifyReportedHostKeys(hostID string, vms []*pb.VMStatus) {
	if s.certs == nil {
		return
	}
	for _, v := range vms {
		pub := v.GetSshHostPubkey()
		if pub == "" {
			continue
		}
		vmID := v.GetVmId()
		if err := s.certTrack.writeThrough(vmID, pub, func() error {
			return s.signAndRecordHostCert(hostID, vmID, pub)
		}); err != nil {
			slog.Warn("sign guest host cert", "vm", vmID, "host", hostID, "err", err)
		}
	}
}