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
+}