From 02e1598eb3f7d4441b1f47892f7e073bccc5ca35 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Wed, 17 Mar 2021 22:39:00 +0000 Subject: [PATCH] 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 --- cluster/prodvider/BUILD.bazel | 1 + cluster/prodvider/crdb.go | 107 ++++++++++++++++++++++++ cluster/prodvider/proto/prodvider.proto | 12 +++ cluster/prodvider/service.go | 9 ++ 4 files changed, 129 insertions(+) create mode 100644 cluster/prodvider/crdb.go diff --git a/cluster/prodvider/BUILD.bazel b/cluster/prodvider/BUILD.bazel index c15ab668..81689c03 100644 --- a/cluster/prodvider/BUILD.bazel +++ b/cluster/prodvider/BUILD.bazel @@ -5,6 +5,7 @@ go_library( 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 00000000..348754c8 --- /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 ba5bf9db..3987f9aa 100644 --- a/cluster/prodvider/proto/prodvider.proto +++ b/cluster/prodvider/proto/prodvider.proto @@ -16,6 +16,7 @@ message AuthenticateResponse { Result result = 1; KubernetesKeys kubernetes_keys = 2; HSPKIKeys hspki_keys = 3; + CockroachDBKeys crdb_keys = 4; } message KubernetesKeys { @@ -32,6 +33,17 @@ message HSPKIKeys { 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 160f2608..19f70ed4 100644 --- a/cluster/prodvider/service.go +++ b/cluster/prodvider/service.go @@ -81,10 +81,19 @@ func (p *prodvider) Authenticate(ctx context.Context, req *pb.AuthenticateReques 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 }