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/credentials.go b/metropolis/cli/metroctl/credentials.go
new file mode 100644
index 0000000..5c23f50
--- /dev/null
+++ b/metropolis/cli/metroctl/credentials.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+	"crypto/ed25519"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/adrg/xdg"
+)
+
+var noCredentialsError = errors.New("owner certificate or key does not exist")
+
+// getCredentials returns Metropolis credentials (if any) from the current
+// metroctl config directory.
+func getCredentials() (cert *x509.Certificate, key ed25519.PrivateKey, err error) {
+	ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
+	if os.IsNotExist(err) {
+		return nil, nil, noCredentialsError
+	} else if err != nil {
+		return nil, nil, fmt.Errorf("failed to load owner private key: %w", err)
+	}
+	block, _ := pem.Decode(ownerPrivateKeyPEM)
+	if block == nil {
+		return nil, nil, errors.New("owner-key.pem contains invalid PEM armoring")
+	}
+	if block.Type != ownerKeyType {
+		return nil, nil, fmt.Errorf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType)
+	}
+	if len(block.Bytes) != ed25519.PrivateKeySize {
+		return nil, nil, errors.New("owner-key.pem contains a non-Ed25519 key")
+	}
+	key = block.Bytes
+	ownerCertPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner.pem"))
+	if os.IsNotExist(err) {
+		return nil, nil, noCredentialsError
+	} else if err != nil {
+		return nil, nil, fmt.Errorf("failed to load owner certificate: %w", err)
+	}
+	block, _ = pem.Decode(ownerCertPEM)
+	if block == nil {
+		return nil, nil, errors.New("owner.pem contains invalid PEM armoring")
+	}
+	if block.Type != "CERTIFICATE" {
+		return nil, nil, fmt.Errorf("owner.pem contains a PEM block that's not a CERTIFICATE")
+	}
+	cert, err = x509.ParseCertificate(block.Bytes)
+	if err != nil {
+		return nil, nil, fmt.Errorf("owner.pem contains an invalid X.509 certificate: %w", err)
+	}
+	return
+}