prod{access,vider}: implement

Prodaccess/Prodvider allow issuing short-lived certificates for all SSO
users to access the kubernetes cluster.

Currently, all users get a personal-$username namespace in which they
have adminitrative rights. Otherwise, they get no access.

In addition, we define a static CRB to allow some admins access to
everything. In the future, this will be more granular.

We also update relevant documentation.

Change-Id: Ia18594eea8a9e5efbb3e9a25a04a28bbd6a42153
diff --git a/cluster/prodvider/main.go b/cluster/prodvider/main.go
new file mode 100644
index 0000000..7222a86
--- /dev/null
+++ b/cluster/prodvider/main.go
@@ -0,0 +1,149 @@
+package main
+
+import (
+	"flag"
+	"io/ioutil"
+	"math/rand"
+	"net"
+	"os"
+	"time"
+
+	"github.com/cloudflare/cfssl/config"
+	"github.com/cloudflare/cfssl/signer/local"
+	"github.com/golang/glog"
+	"google.golang.org/grpc"
+	"k8s.io/client-go/kubernetes"
+
+	pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+)
+
+var (
+	flagLDAPServer          string
+	flagLDAPBindDN          string
+	flagLDAPGroupSearchBase string
+	flagListenAddress       string
+	flagKubernetesHost      string
+
+	flagCACertificatePath     string
+	flagCAKeyPath             string
+	flagKubeCACertificatePath string
+
+	flagProdviderCN string
+)
+
+func init() {
+	flag.Set("logtostderr", "true")
+}
+
+type prodvider struct {
+	sign      *local.Signer
+	k8s       *kubernetes.Clientset
+	srv       *grpc.Server
+	kubeCAPEM []byte
+}
+
+func newProdvider() *prodvider {
+	policy := &config.Signing{
+		Profiles: map[string]*config.SigningProfile{
+			"server": &config.SigningProfile{
+				Usage:        []string{"signing", "key encipherment", "server auth"},
+				ExpiryString: "30d",
+			},
+			"client": &config.SigningProfile{
+				Usage:        []string{"signing", "key encipherment", "client auth"},
+				ExpiryString: "30d",
+			},
+			"client-server": &config.SigningProfile{
+				Usage:        []string{"signing", "key encipherment", "server auth", "client auth"},
+				ExpiryString: "30d",
+			},
+		},
+		Default: config.DefaultConfig(),
+	}
+
+	sign, err := local.NewSignerFromFile(flagCACertificatePath, flagCAKeyPath, policy)
+	if err != nil {
+		glog.Exitf("Could not create signer: %v", err)
+	}
+
+	kubeCAPEM, err := ioutil.ReadFile(flagKubeCACertificatePath)
+	if err != nil {
+		glog.Exitf("Could not read kube CA cert path: %v")
+	}
+
+	return &prodvider{
+		sign:      sign,
+		kubeCAPEM: kubeCAPEM,
+	}
+}
+
+// Timebomb restarts the prodvider after a deadline, usually 7 days +/- 4 days.
+// This is to ensure we serve with up-to-date certificates and that the service
+// can still come up after restart.
+func timebomb(srv *grpc.Server) {
+	deadline := time.Now()
+	deadline = deadline.Add(3 * 24 * time.Hour)
+	rand.Seed(time.Now().UnixNano())
+	jitter := rand.Intn(8 * 24 * 60 * 60)
+	deadline = deadline.Add(time.Duration(jitter) * time.Second)
+
+	glog.Infof("Timebomb deadline set to %v", deadline)
+
+	t := time.NewTicker(time.Minute)
+	for {
+		<-t.C
+		if time.Now().After(deadline) {
+			break
+		}
+	}
+
+	// Start killing connections, and wait one minute...
+	go srv.GracefulStop()
+	<-t.C
+	glog.Infof("Timebomb deadline exceeded, restarting.")
+	os.Exit(0)
+}
+
+func main() {
+	flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "Address of LDAP server")
+	flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "uid=%s,ou=People,dc=hackerspace,dc=pl", "LDAP Bind DN")
+	flag.StringVar(&flagLDAPGroupSearchBase, "ldap_group_search_base_dn", "ou=Group,dc=hackerspace,dc=pl", "LDAP Group Search Base DN")
+	flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:8080", "gRPC listen address")
+	flag.StringVar(&flagKubernetesHost, "kubernetes_host", "k0.hswaw.net:4001", "Kubernetes API host")
+
+	flag.StringVar(&flagCACertificatePath, "ca_certificate_path", "", "CA certificate path (for signer)")
+	flag.StringVar(&flagCAKeyPath, "ca_key_path", "", "CA key path (for signer)")
+	flag.StringVar(&flagKubeCACertificatePath, "kube_ca_certificate_path", "", "CA certificate path (for checking kube apiserver)")
+
+	flag.StringVar(&flagProdviderCN, "prodvider_cn", "prodvider.hswaw.net", "CN of certificate that prodvider will use")
+	flag.Parse()
+
+	if flagCACertificatePath == "" || flagCAKeyPath == "" {
+		glog.Exitf("CA certificate and key must be provided")
+	}
+
+	p := newProdvider()
+	err := p.kubernetesConnect()
+	if err != nil {
+		glog.Exitf("Could not connect to kubernetes: %v", err)
+	}
+	creds := p.selfCreds()
+
+	// Start serving gRPC
+	grpcLis, err := net.Listen("tcp", flagListenAddress)
+	if err != nil {
+		glog.Exitf("Could not listen for gRPC on %q: %v", flagListenAddress, err)
+	}
+
+	glog.Infof("Starting gRPC on %q...", flagListenAddress)
+	grpcSrv := grpc.NewServer(creds)
+
+	pb.RegisterProdviderServer(grpcSrv, p)
+
+	go timebomb(grpcSrv)
+
+	err = grpcSrv.Serve(grpcLis)
+	if err != nil {
+		glog.Exitf("Could not serve gRPC: %v", err)
+	}
+}