core/internal/kubernetes: refactor PKI fully

We move ad-hoc certificate/key creation to a little declarative,
future-inspired API.

The API is split into two distinct layers:
 - an etcd-backed managed certificate storage that understands server
   certificates, client certificates and CAs
 - a Kubernetes PKI object, that understands what certificates are
   needed to bring up a cluster

This allows for deduplicated path names in etcd, some semantic
information about available certificates, and is in general groundwork
for some future improvements, like:
 - a slightly higher level etcd 'data store' api, with
   less-stringly-typed paths
 - simplification of service startup code (there's a bunch of cleanups
   that can be still done in core/internal/kubernetes wrt. to
   certificate marshaling to the filesystem, etc)

Test Plan: covered by existing tests - but this should also now be nicely testable in isolation!

X-Origin-Diff: phab/D564
GitOrigin-RevId: a58620c37ac064a15b7db106b7a5cbe9bd0b7cd0
diff --git a/core/internal/kubernetes/apiserver.go b/core/internal/kubernetes/apiserver.go
index 9bc32f3..0a740dd 100644
--- a/core/internal/kubernetes/apiserver.go
+++ b/core/internal/kubernetes/apiserver.go
@@ -19,19 +19,17 @@
 import (
 	"context"
 	"encoding/pem"
-	"errors"
 	"fmt"
 	"io"
 	"net"
 	"os/exec"
-	"path"
 
 	"git.monogon.dev/source/nexantic.git/core/internal/common"
+	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
+	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/pki"
+	"git.monogon.dev/source/nexantic.git/core/pkg/fileargs"
 
 	"go.etcd.io/etcd/clientv3"
-
-	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
-	"git.monogon.dev/source/nexantic.git/core/pkg/fileargs"
 )
 
 type apiserverConfig struct {
@@ -49,22 +47,37 @@
 	serverKey             []byte
 }
 
-func getPKIApiserverConfig(consensusKV clientv3.KV) (*apiserverConfig, error) {
+func getPKIApiserverConfig(ctx context.Context, kv clientv3.KV, kpki *pki.KubernetesPKI) (*apiserverConfig, error) {
 	var config apiserverConfig
+
+	for _, el := range []struct {
+		targetCert *[]byte
+		targetKey  *[]byte
+		name       pki.KubeCertificateName
+	}{
+		{&config.idCA, nil, pki.IdCA},
+		{&config.kubeletClientCert, &config.kubeletClientKey, pki.KubeletClient},
+		{&config.aggregationCA, nil, pki.AggregationCA},
+		{&config.aggregationClientCert, &config.aggregationClientKey, pki.FrontProxyClient},
+		{&config.serverCert, &config.serverKey, pki.APIServer},
+	} {
+		cert, key, err := kpki.Certificate(ctx, el.name, kv)
+		if err != nil {
+			return nil, fmt.Errorf("could not load certificate %q from PKI: %w", el.name, err)
+		}
+		if el.targetCert != nil {
+			*el.targetCert = cert
+		}
+		if el.targetKey != nil {
+			*el.targetKey = key
+		}
+	}
+
 	var err error
-	config.idCA, _, err = getCert(consensusKV, "id-ca")
-	config.kubeletClientCert, config.kubeletClientKey, err = getCert(consensusKV, "kubelet-client")
-	config.aggregationCA, _, err = getCert(consensusKV, "aggregation-ca")
-	config.aggregationClientCert, config.aggregationClientKey, err = getCert(consensusKV, "front-proxy-client")
-	config.serverCert, config.serverKey, err = getCert(consensusKV, "apiserver")
-	saPrivkey, err := consensusKV.Get(context.Background(), path.Join(etcdPath, "service-account-privkey.der"))
+	config.serviceAccountPrivKey, err = kpki.ServiceAccountKey(ctx, kv)
 	if err != nil {
-		return nil, fmt.Errorf("failed to get serviceaccount privkey: %w", err)
+		return nil, fmt.Errorf("could not load serviceaccount privkey: %w", err)
 	}
-	if len(saPrivkey.Kvs) != 1 {
-		return nil, errors.New("failed to get serviceaccount privkey: not found")
-	}
-	config.serviceAccountPrivKey = saPrivkey.Kvs[0].Value
 	return &config, nil
 }