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