cluster/prodvider: emit crdb certs

This emits short-lived user credentials for a `dev-user` in crdb-waw1
any time someone prodaccesses.

Change-Id: I0266a05c1f02225d762cfd2ca61976af0658639d
diff --git a/cluster/prodvider/BUILD.bazel b/cluster/prodvider/BUILD.bazel
index c15ab66..81689c0 100644
--- a/cluster/prodvider/BUILD.bazel
+++ b/cluster/prodvider/BUILD.bazel
@@ -5,6 +5,7 @@
     name = "go_default_library",
     srcs = [
         "certs.go",
+        "crdb.go",
         "hspki.go",
         "kubernetes.go",
         "main.go",
diff --git a/cluster/prodvider/crdb.go b/cluster/prodvider/crdb.go
new file mode 100644
index 0000000..348754c
--- /dev/null
+++ b/cluster/prodvider/crdb.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+	"context"
+	"encoding/pem"
+	"fmt"
+	"time"
+
+	"github.com/cloudflare/cfssl/config"
+	"github.com/cloudflare/cfssl/csr"
+	"github.com/cloudflare/cfssl/helpers"
+	"github.com/cloudflare/cfssl/signer"
+	"github.com/cloudflare/cfssl/signer/local"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+)
+
+// crdbSigner returns a cfssl signer (CA) for a crdb cluster, by loading the CA
+// cert/key from Kubernetes.
+func (p *prodvider) crdbSigner(ctx context.Context, cluster string) (*local.Signer, error) {
+	policy := &config.Signing{
+		Profiles: map[string]*config.SigningProfile{
+			"client": &config.SigningProfile{
+				Usage:        []string{"signing", "key encipherment"},
+				ExpiryString: "30d",
+			},
+		},
+		Default: config.DefaultConfig(),
+	}
+
+	namespace := fmt.Sprintf("crdb-%s", cluster)
+
+	secret, err := p.k8s.CoreV1().Secrets(namespace).Get(ctx, "cluster-ca", metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("hspki secret get failed: %w", err)
+	}
+
+	parsedCa, err := helpers.ParseCertificatePEM(secret.Data["tls.crt"])
+	if err != nil {
+		return nil, fmt.Errorf("when parsing tls.crt: %w", err)
+	}
+
+	priv, err := helpers.ParsePrivateKeyPEMWithPassword(secret.Data["tls.key"], nil)
+	if err != nil {
+		return nil, fmt.Errorf("when parsing tls.key: %w", err)
+	}
+
+	return local.NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), policy)
+}
+
+// crdbCreds returns a crdb certificate/key for an SSO useron a given cluster.
+// The returned certificate is valid for connecting to crdb.
+func (p *prodvider) crdbCreds(ctx context.Context, username, cluster string) (*pb.CockroachDBKeys_Cluster, error) {
+	username = fmt.Sprintf("dev-%s", username)
+
+	s, err := p.crdbSigner(ctx, cluster)
+	if err != nil {
+		return nil, fmt.Errorf("hspkiSigner: %w", err)
+	}
+
+	signerCert, _ := s.Certificate("", "")
+	req := &csr.CertificateRequest{
+		CN: username,
+		KeyRequest: &csr.BasicKeyRequest{
+			A: "rsa",
+			S: 4096,
+		},
+		Names: []csr.Name{
+			{
+				O: "prodvider",
+			},
+		},
+		Hosts: []string{username},
+	}
+
+	g := &csr.Generator{
+		Validator: func(req *csr.CertificateRequest) error { return nil },
+	}
+
+	csrPEM, keyPEM, err := g.ProcessRequest(req)
+	if err != nil {
+		return nil, fmt.Errorf("when making CSR: %w", err)
+	}
+
+	signReq := signer.SignRequest{
+		Hosts:    []string{username},
+		Request:  string(csrPEM),
+		Profile:  "client",
+		NotAfter: time.Now().Add(9 * time.Hour),
+	}
+
+	certPEM, err := s.Sign(signReq)
+	if err != nil {
+		return nil, fmt.Errorf("when issuing certificate: %w", err)
+	}
+
+	caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Raw})
+
+	return &pb.CockroachDBKeys_Cluster{
+		Name:     cluster,
+		Ca:       caPEM,
+		Cert:     certPEM,
+		Key:      keyPEM,
+		Username: username,
+	}, nil
+}
diff --git a/cluster/prodvider/proto/prodvider.proto b/cluster/prodvider/proto/prodvider.proto
index ba5bf9d..3987f9a 100644
--- a/cluster/prodvider/proto/prodvider.proto
+++ b/cluster/prodvider/proto/prodvider.proto
@@ -16,6 +16,7 @@
     Result result = 1;
     KubernetesKeys kubernetes_keys = 2;
     HSPKIKeys hspki_keys = 3;
+    CockroachDBKeys crdb_keys = 4;
 }
 
 message KubernetesKeys {
@@ -32,6 +33,17 @@
     string principal = 4;
 }
 
+message CockroachDBKeys {
+    message Cluster {
+        string name = 1;
+        bytes ca = 2;
+        bytes cert = 3;
+        bytes key = 4;
+        string username = 5;
+    }
+    repeated Cluster clusters = 2;
+}
+
 service Prodvider {
     rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
 }
diff --git a/cluster/prodvider/service.go b/cluster/prodvider/service.go
index 160f260..19f70ed 100644
--- a/cluster/prodvider/service.go
+++ b/cluster/prodvider/service.go
@@ -81,10 +81,19 @@
 		return nil, status.Error(codes.Unavailable, "could not generate hspki keys")
 	}
 
+	crdbWaw1Keys, err := p.crdbCreds(ctx, username, "waw1")
+	if err != nil {
+		glog.Errorf("crdbCreds(%q): %v", username, err)
+		return nil, status.Error(codes.Unavailable, "could not generate crdb keys")
+	}
+
 	return &pb.AuthenticateResponse{
 		Result:         pb.AuthenticateResponse_RESULT_AUTHENTICATED,
 		KubernetesKeys: kubernetesKeys,
 		HspkiKeys:      hspkiKeys,
+		CrdbKeys: &pb.CockroachDBKeys{
+			Clusters: []*pb.CockroachDBKeys_Cluster{crdbWaw1Keys},
+		},
 	}, nil
 }