blob: ca114f082fbe434530ffc136da190ae5ae223efc [file] [log] [blame]
package main
import (
"crypto/ed25519"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"math/rand"
"net"
"os"
"time"
"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 {
k8s *kubernetes.Clientset
srv *grpc.Server
intermediateCAKey ed25519.PrivateKey
intermediateCACert *x509.Certificate
kubeCACert *x509.Certificate
}
func loadCert(path string) (*x509.Certificate, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode(b)
if block == nil {
return nil, fmt.Errorf("no PEM block found")
}
if block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
}
return x509.ParseCertificate(block.Bytes)
}
func loadKey(path string) (ed25519.PrivateKey, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode(bytes)
if block == nil {
return nil, fmt.Errorf("no PEM block found")
}
if block.Type != "PRIVATE KEY" {
return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
if k, ok := key.(ed25519.PrivateKey); ok {
return k, nil
}
return nil, fmt.Errorf("not an ED25519 key")
}
func newProdvider() *prodvider {
kubeCACert, err := loadCert(flagKubeCACertificatePath)
if err != nil {
glog.Exitf("Loading kube CA certificate failed: %v", err)
}
intermediateCACert, err := loadCert(flagCACertificatePath)
if err != nil {
glog.Exitf("Loading intermediate CAcertificate failed: %v", err)
}
intermediateCAKey, err := loadKey(flagCAKeyPath)
return &prodvider{
intermediateCAKey: intermediateCAKey,
intermediateCACert: intermediateCACert,
kubeCACert: kubeCACert,
}
}
// 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)
}
}