m/c/metroctl: add k8s credentials plugin

This adds a command implementing the K8s client-go credentials
interface. It provides Metropolis credentials to Kubernetes clients
like kubectl for use with an authenticating proxy being added later.

Change-Id: I11d29f80134c2ec0839f0619eaebc4a4bb2aa3e0
Reviewed-on: https://review.monogon.dev/c/monogon/+/508
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/cli/metroctl/k8scredplugin.go b/metropolis/cli/metroctl/k8scredplugin.go
new file mode 100644
index 0000000..d2e591a
--- /dev/null
+++ b/metropolis/cli/metroctl/k8scredplugin.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"log"
+	"os"
+
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
+)
+
+var k8scredpluginCmd = &cobra.Command{
+	Use:   "k8scredplugin",
+	Short: "Kubernetes client-go credential plugin [internal use]",
+	Long: `This implements a Kubernetes client-go credential plugin to
+authenticate client-go based callers including kubectl against a Metropolis
+cluster. This should never be directly called by end users.`,
+	Args: cobra.ExactArgs(0),
+	Run:  doK8sCredPlugin,
+}
+
+func doK8sCredPlugin(cmd *cobra.Command, args []string) {
+	cert, key, err := getCredentials()
+	if err == noCredentialsError {
+		log.Fatal("No credentials found on your machine")
+	}
+	if err != nil {
+		log.Fatalf("failed to get Metropolis credentials: %v", err)
+	}
+
+	pkcs8Key, err := x509.MarshalPKCS8PrivateKey(key)
+	if err != nil {
+		// We explicitly pass an Ed25519 private key in, so this can't happen
+		panic(err)
+	}
+
+	cred := clientauthentication.ExecCredential{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: clientauthentication.SchemeGroupVersion.String(),
+			Kind:       "ExecCredential",
+		},
+		Status: &clientauthentication.ExecCredentialStatus{
+			ClientCertificateData: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})),
+			ClientKeyData:         string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8Key})),
+		},
+	}
+	if err := json.NewEncoder(os.Stdout).Encode(cred); err != nil {
+		log.Fatalf("failed to encode ExecCredential: %v", err)
+	}
+}
+
+func init() {
+	rootCmd.AddCommand(k8scredpluginCmd)
+}