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/cmd_admincreds.go b/cluster/clustercfg/cmd_admincreds.go
new file mode 100644
index 0000000..110c54d
--- /dev/null
+++ b/cluster/clustercfg/cmd_admincreds.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/user"
+	"path/filepath"
+
+	"github.com/spf13/cobra"
+	"k8s.io/client-go/tools/clientcmd"
+	clientapi "k8s.io/client-go/tools/clientcmd/api"
+
+	"code.hackerspace.pl/hscloud/cluster/clustercfg/certs"
+	"code.hackerspace.pl/hscloud/go/workspace"
+)
+
+var admincredsCmd = &cobra.Command{
+	Use:   "admincreds",
+	Short: "Acquire emergency Kubernetes credentials",
+	Long: `
+Use secretstore secrets to generate a Kubernetes system:masters keypair and
+certificate. Only for use in emergencies.
+
+Your local username and hostname will make part of the cert and can be used
+for auditing of accesses to apiservers.
+`,
+	Run: func(cmd *cobra.Command, args []string) {
+		ws, err := workspace.Get()
+		if err != nil {
+			log.Fatalf("Could not figure out workspace: %v", err)
+		}
+
+		uname := "UNKNOWN"
+		if u, err := user.Current(); err == nil {
+			uname = u.Username
+		}
+		hostname := "UNKNOWN"
+		if h, err := os.Hostname(); err == nil {
+			hostname = h
+		}
+		breadcrumb := fmt.Sprintf("%s@%s", uname, hostname)
+
+		root := filepath.Join(ws, "cluster")
+		path := filepath.Join(ws, ".kubectl", "admincreds")
+		c := certs.Prepare(root, nil)
+		creds := c.MakeKubeEmergencyCreds(path, breadcrumb)
+		_ = creds
+
+		log.Printf("")
+		log.Printf("WARNING WARNING WARNING WARNING WARNING WARNING")
+		log.Printf("===============================================")
+		log.Printf("")
+		log.Printf("You are requesting ADMIN credentials.")
+		log.Printf("")
+		log.Printf("You likely shouldn't be doing this, and")
+		log.Printf("instead should be using `prodaccess`.")
+		log.Printf("")
+		log.Printf("===============================================")
+		log.Printf("WARNING WARNING WARNING WARNING WARNING WARNING")
+		log.Printf("")
+
+		log.Printf("Issuing certs...")
+		if err := creds.Ensure(); err != nil {
+			log.Fatalf("Failed: %v", err)
+		}
+
+		log.Printf("Configuring kubectl...")
+		caPath, certPath, keyPath := creds.Paths()
+		if err := installKubeletConfig(caPath, certPath, keyPath, "emergency.k0"); err != nil {
+			log.Fatalf("Failed: %v", err)
+		}
+
+		log.Fatalf("Done. Use kubectl --context=emergency.k0")
+	},
+}
+
+func installKubeletConfig(caPath, certPath, keyPath, configName string) error {
+	ca := clientcmd.NewDefaultPathOptions()
+	config, err := ca.GetStartingConfig()
+	if err != nil {
+		return fmt.Errorf("getting initial config failed: %w", err)
+	}
+
+	config.AuthInfos[configName] = &clientapi.AuthInfo{
+		ClientCertificate: certPath,
+		ClientKey:         keyPath,
+	}
+
+	config.Clusters[configName] = &clientapi.Cluster{
+		CertificateAuthority: caPath,
+		Server:               "https://k0.hswaw.net:4001",
+	}
+
+	config.Contexts[configName] = &clientapi.Context{
+		AuthInfo:  configName,
+		Cluster:   configName,
+		Namespace: "default",
+	}
+
+	if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
+		return fmt.Errorf("modifying config failed: %w", err)
+	}
+	return nil
+}
+
+func init() {
+	rootCmd.AddCommand(admincredsCmd)
+}