cluster/clustercfg: rewrite it in Go

This replaces the old clustercfg script with a brand spanking new
mostly-equivalent Go reimplementation. But it's not exactly the same,
here are the differences:

 1. No cluster deployment logic anymore - we expect everyone to use ops/
    machine at this point.
 2. All certs/keys are Ed25519 and do not expire by default - but
    support for short-lived certificates is there, and is actually more
    generic and reusable. Currently it's only used for admincreds.
 3. Speaking of admincreds: the new admincreds automatically figure out
    your username.
 4. admincreds also doesn't shell out to kubectl anymore, and doesn't
    override your default context. The generated creds can live
    peacefully alongside your normal prodaccess creds.
 5. gencerts (the new nodestrap without deployment support) now
    automatically generates certs for all nodes, based on local Nix
    modules in ops/.
 6. No secretstore support. This will be changed once we rebuild
    secretstore in Go. For now users are expected to manually run
    secretstore sync on cluster/secrets.

Change-Id: Ida935f44e04fd933df125905eee10121ac078495
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1498
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/cluster/clustercfg/certs/certs.go b/cluster/clustercfg/certs/certs.go
new file mode 100644
index 0000000..7571717
--- /dev/null
+++ b/cluster/clustercfg/certs/certs.go
@@ -0,0 +1,288 @@
+package certs
+
+import (
+	"net"
+	"time"
+)
+
+// Certificates is the set of certificates required to run our Kubernetes
+// production.
+type Certificates struct {
+	CAs CAs
+
+	ProdviderIntermediateCA *Certificate
+
+	Global  Global
+	PerNode map[string]PerNode
+}
+
+type ensurer interface {
+	Ensure() error
+}
+
+// Ensure checks that all the Kubernetes production certificates and keys are
+// present on disk, generating them as necessary.
+//
+// If the user has not decrypted cluster/secrets, an error will be returned.
+// However, deeper sync checks are not currently performed.
+func (c *Certificates) Ensure() error {
+	sub := []ensurer{
+		&c.CAs,
+		c.ProdviderIntermediateCA,
+		&c.Global,
+	}
+	for _, pn := range c.PerNode {
+		sub = append(sub, &pn)
+	}
+	for _, s := range sub {
+		if err := s.Ensure(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// CAs are the root certificate authorities we use.
+type CAs struct {
+	// EtcdPeer is used by etcd member nodes to authenticate peers.
+	EtcdPeer *Certificate
+	// Etcd is used by etcd membrer nodes to authenticate clients (ie.
+	// kube-apiservers).
+	Etcd *Certificate
+	// Kube is the main Kubernetes 'identity' CA, used to identify components
+	// and users.
+	Kube *Certificate
+	// KubeFront is the proxy/aggregation CA used by external apiservices to
+	// authenticate incoming apiserver connections.
+	KubeFront *Certificate
+	// Admitomatic is the CA used by the admitomatic webhook to authenticate
+	// incoming apiserver connections.
+	Admitomatic *Certificate
+}
+
+func (c *CAs) Ensure() error {
+	sub := []ensurer{
+		c.EtcdPeer,
+		c.Etcd,
+		c.Kube,
+		c.KubeFront,
+		c.Admitomatic,
+	}
+	for _, s := range sub {
+		if err := s.Ensure(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Global are all the non-per-node certificates we use.
+type Global struct {
+	// EtcdKube is used by kubernetes apiservers to authenticate to etcd
+	// members.
+	EtcdKube *Certificate
+
+	// KubeApiserver is used by kubernetes apiservers to authenticate to other
+	// kubernetes components/users.
+	KubeApiserver *Certificate
+	// KubeControllerManager is used by kubernetes controller managers to
+	// authenticate to the kubernetes apiservers.
+	KubeControllerManager *Certificate
+	// KubeScheduler is used by the kubernetes schedulers to authenticate to
+	// the kubernetes apiservers.
+	KubeScheduler *Certificate
+
+	// KubefrontApiserver is used by the kubernetes apiserver to authenticate
+	// to external apiservices.
+	KubefrontApiserver *Certificate
+
+	// AdmitomaticWebhook is used by the admitomatic webhook to authenticate to
+	// the Kubernetes apiservers.
+	AdmitomaticWebhook *Certificate
+}
+
+func (g *Global) Ensure() error {
+	sub := []ensurer{
+		g.EtcdKube,
+		g.KubeApiserver,
+		g.KubeControllerManager,
+		g.KubeScheduler,
+		g.KubefrontApiserver,
+		g.AdmitomaticWebhook,
+	}
+	for _, s := range sub {
+		if err := s.Ensure(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (c *Certificates) MakeKubeEmergencyCreds(root, breadcrumb string) *Certificate {
+	return &Certificate{
+		name:     "emergency",
+		duration: 7 * 24 * time.Hour,
+		root:     root,
+		kind:     kindClient,
+		cn:       "admin",
+		san:      []string{"admin", breadcrumb},
+		o:        "system:masters",
+		issuer:   c.CAs.Kube,
+	}
+}
+
+// Per node are all the per-node certificates we use.
+type PerNode struct {
+	// EtcdPeer is used by etcd members to authenticate to other etcd members.
+	EtcdPeer *Certificate
+	// EtcdClient is used by etcd members to authenticate to their clients.
+	EtcdClient *Certificate
+
+	// Kubelet is used by kubelets to authenticate to other kubernetes
+	// components.
+	Kubelet *Certificate
+}
+
+func (p *PerNode) Ensure() error {
+	sub := []ensurer{
+		p.EtcdPeer,
+		p.EtcdClient,
+		p.Kubelet,
+	}
+	for _, s := range sub {
+		if err := s.Ensure(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func mkCA(root, name, cn string) *Certificate {
+	return &Certificate{
+		name: name,
+		root: root,
+		kind: kindCA,
+		cn:   cn,
+	}
+}
+
+// Prepare builds our Certificates structure at a given location on the
+// filesystem, for the given nodes.
+//
+// Calling Ensure() on the returned Certificates will actually engage
+// generation logic. Before that, no disk accesses are performed.
+func Prepare(root string, fqdns []string) Certificates {
+	certs := Certificates{
+		CAs: CAs{
+			EtcdPeer:    mkCA(root, "ca-etcdpeer", "etcd peer ca"),
+			Etcd:        mkCA(root, "ca-etcd", "etcd ca"),
+			Kube:        mkCA(root, "ca-kube", "kubernetes main CA"),
+			KubeFront:   mkCA(root, "ca-kubefront", "kubernetes frontend CA"),
+			Admitomatic: mkCA(root, "ca-admitomatic", "admitomatic webhook CA"),
+		},
+		PerNode: make(map[string]PerNode),
+	}
+
+	certs.ProdviderIntermediateCA = &Certificate{
+		name:   "ca-kube-prodvider",
+		root:   root,
+		kind:   kindProdvider,
+		cn:     "kubernetes prodvider intermediate",
+		issuer: certs.CAs.Kube,
+	}
+	certs.Global = Global{
+		EtcdKube: &Certificate{
+			name:   "etcd-kube",
+			root:   root,
+			kind:   kindClient,
+			cn:     "kube etcd client certificate",
+			san:    []string{"kube"},
+			issuer: certs.CAs.Etcd,
+		},
+		KubeApiserver: &Certificate{
+			name: "kube-apiserver",
+			root: root,
+			kind: kindClientServer,
+			cn:   "k0.hswaw.net",
+			san: []string{
+				"k0.hswaw.net",
+				"kubernetes.default.svc.k0.hswaw.net",
+			},
+			ips: []net.IP{
+				{10, 10, 12, 1},
+			},
+			issuer: certs.CAs.Kube,
+		},
+		KubeControllerManager: &Certificate{
+			name:   "kube-controllermanager",
+			root:   root,
+			kind:   kindClientServer,
+			cn:     "system:kube-controller-manager",
+			san:    []string{"system:kube-controller-manager"},
+			o:      "system:kube-controller-manager",
+			issuer: certs.CAs.Kube,
+		},
+		KubeScheduler: &Certificate{
+			name:   "kube-scheduler",
+			root:   root,
+			kind:   kindClientServer,
+			cn:     "system:kube-scheduler",
+			san:    []string{"system:kube-scheduler"},
+			o:      "system:kube-scheduler",
+			issuer: certs.CAs.Kube,
+		},
+		KubefrontApiserver: &Certificate{
+			name:   "kubefront-apiserver",
+			root:   root,
+			kind:   kindClientServer,
+			cn:     "Kubernetes Frontend",
+			san:    []string{"apiserver"},
+			issuer: certs.CAs.KubeFront,
+		},
+		AdmitomaticWebhook: &Certificate{
+			name:   "admitomatic-webhook",
+			root:   root,
+			kind:   kindServer,
+			cn:     "Admitomatic Webhook",
+			san:    []string{"admitomatic.admitomatic.svc"},
+			issuer: certs.CAs.Admitomatic,
+		},
+	}
+	for _, fqdn := range fqdns {
+		certs.PerNode[fqdn] = PerNode{
+			EtcdPeer: &Certificate{
+				name:   "etcdpeer-" + fqdn,
+				root:   root,
+				kind:   kindClientServer,
+				cn:     "node etcd peer certificate",
+				san:    []string{fqdn},
+				issuer: certs.CAs.EtcdPeer,
+			},
+			EtcdClient: &Certificate{
+				name: "etcd-" + fqdn,
+				root: root,
+				// etcd seems to need client too, as it's connecting to itself
+				// for... some reason?
+				// https://github.com/etcd-io/etcd/issues/9785
+				kind:   kindClientServer,
+				cn:     "node etcd server certificate",
+				san:    []string{fqdn},
+				issuer: certs.CAs.Etcd,
+			},
+			Kubelet: &Certificate{
+				name: "kube-kubelet-" + fqdn,
+				root: root,
+				kind: kindClientServer,
+				cn:   "system:node:" + fqdn,
+				o:    "system:nodes",
+				san: []string{
+					"system:node:" + fqdn,
+					fqdn,
+				},
+				issuer: certs.CAs.Kube,
+			},
+		}
+	}
+
+	return certs
+}