Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 4 | "crypto/ed25519" |
| 5 | "crypto/x509" |
| 6 | "encoding/pem" |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 7 | "flag" |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 8 | "fmt" |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 9 | "math/rand" |
| 10 | "net" |
| 11 | "os" |
| 12 | "time" |
| 13 | |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 14 | "github.com/golang/glog" |
| 15 | "google.golang.org/grpc" |
| 16 | "k8s.io/client-go/kubernetes" |
| 17 | |
| 18 | pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto" |
| 19 | ) |
| 20 | |
| 21 | var ( |
| 22 | flagLDAPServer string |
| 23 | flagLDAPBindDN string |
| 24 | flagLDAPGroupSearchBase string |
| 25 | flagListenAddress string |
| 26 | flagKubernetesHost string |
| 27 | |
| 28 | flagCACertificatePath string |
| 29 | flagCAKeyPath string |
| 30 | flagKubeCACertificatePath string |
| 31 | |
| 32 | flagProdviderCN string |
| 33 | ) |
| 34 | |
| 35 | func init() { |
| 36 | flag.Set("logtostderr", "true") |
| 37 | } |
| 38 | |
| 39 | type prodvider struct { |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 40 | k8s *kubernetes.Clientset |
| 41 | srv *grpc.Server |
| 42 | |
| 43 | intermediateCAKey ed25519.PrivateKey |
| 44 | intermediateCACert *x509.Certificate |
| 45 | kubeCACert *x509.Certificate |
| 46 | } |
| 47 | |
| 48 | func loadCert(path string) (*x509.Certificate, error) { |
| 49 | b, err := os.ReadFile(path) |
| 50 | if err != nil { |
| 51 | return nil, err |
| 52 | } |
| 53 | |
| 54 | block, _ := pem.Decode(b) |
| 55 | if block == nil { |
| 56 | return nil, fmt.Errorf("no PEM block found") |
| 57 | } |
| 58 | if block.Type != "CERTIFICATE" { |
| 59 | return nil, fmt.Errorf("unexpected PEM block: %q", block.Type) |
| 60 | } |
| 61 | return x509.ParseCertificate(block.Bytes) |
| 62 | } |
| 63 | |
| 64 | func loadKey(path string) (ed25519.PrivateKey, error) { |
| 65 | bytes, err := os.ReadFile(path) |
| 66 | if err != nil { |
| 67 | return nil, err |
| 68 | } |
| 69 | block, _ := pem.Decode(bytes) |
| 70 | if block == nil { |
| 71 | return nil, fmt.Errorf("no PEM block found") |
| 72 | } |
| 73 | if block.Type != "PRIVATE KEY" { |
| 74 | return nil, fmt.Errorf("unexpected PEM block: %q", block.Type) |
| 75 | } |
| 76 | key, err := x509.ParsePKCS8PrivateKey(block.Bytes) |
| 77 | if err != nil { |
| 78 | return nil, err |
| 79 | } |
| 80 | if k, ok := key.(ed25519.PrivateKey); ok { |
| 81 | return k, nil |
| 82 | } |
| 83 | return nil, fmt.Errorf("not an ED25519 key") |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 84 | } |
| 85 | |
| 86 | func newProdvider() *prodvider { |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 87 | kubeCACert, err := loadCert(flagKubeCACertificatePath) |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 88 | if err != nil { |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 89 | glog.Exitf("Loading kube CA certificate failed: %v", err) |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 90 | } |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 91 | intermediateCACert, err := loadCert(flagCACertificatePath) |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 92 | if err != nil { |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 93 | glog.Exitf("Loading intermediate CAcertificate failed: %v", err) |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 94 | } |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 95 | intermediateCAKey, err := loadKey(flagCAKeyPath) |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 96 | return &prodvider{ |
Serge Bazanski | 3a6d67e | 2023-03-31 22:36:27 +0000 | [diff] [blame] | 97 | intermediateCAKey: intermediateCAKey, |
| 98 | intermediateCACert: intermediateCACert, |
| 99 | kubeCACert: kubeCACert, |
Sergiusz Bazanski | b13b7ff | 2019-08-29 20:12:24 +0200 | [diff] [blame] | 100 | } |
| 101 | } |
| 102 | |
| 103 | // Timebomb restarts the prodvider after a deadline, usually 7 days +/- 4 days. |
| 104 | // This is to ensure we serve with up-to-date certificates and that the service |
| 105 | // can still come up after restart. |
| 106 | func timebomb(srv *grpc.Server) { |
| 107 | deadline := time.Now() |
| 108 | deadline = deadline.Add(3 * 24 * time.Hour) |
| 109 | rand.Seed(time.Now().UnixNano()) |
| 110 | jitter := rand.Intn(8 * 24 * 60 * 60) |
| 111 | deadline = deadline.Add(time.Duration(jitter) * time.Second) |
| 112 | |
| 113 | glog.Infof("Timebomb deadline set to %v", deadline) |
| 114 | |
| 115 | t := time.NewTicker(time.Minute) |
| 116 | for { |
| 117 | <-t.C |
| 118 | if time.Now().After(deadline) { |
| 119 | break |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | // Start killing connections, and wait one minute... |
| 124 | go srv.GracefulStop() |
| 125 | <-t.C |
| 126 | glog.Infof("Timebomb deadline exceeded, restarting.") |
| 127 | os.Exit(0) |
| 128 | } |
| 129 | |
| 130 | func main() { |
| 131 | flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "Address of LDAP server") |
| 132 | flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "uid=%s,ou=People,dc=hackerspace,dc=pl", "LDAP Bind DN") |
| 133 | flag.StringVar(&flagLDAPGroupSearchBase, "ldap_group_search_base_dn", "ou=Group,dc=hackerspace,dc=pl", "LDAP Group Search Base DN") |
| 134 | flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:8080", "gRPC listen address") |
| 135 | flag.StringVar(&flagKubernetesHost, "kubernetes_host", "k0.hswaw.net:4001", "Kubernetes API host") |
| 136 | |
| 137 | flag.StringVar(&flagCACertificatePath, "ca_certificate_path", "", "CA certificate path (for signer)") |
| 138 | flag.StringVar(&flagCAKeyPath, "ca_key_path", "", "CA key path (for signer)") |
| 139 | flag.StringVar(&flagKubeCACertificatePath, "kube_ca_certificate_path", "", "CA certificate path (for checking kube apiserver)") |
| 140 | |
| 141 | flag.StringVar(&flagProdviderCN, "prodvider_cn", "prodvider.hswaw.net", "CN of certificate that prodvider will use") |
| 142 | flag.Parse() |
| 143 | |
| 144 | if flagCACertificatePath == "" || flagCAKeyPath == "" { |
| 145 | glog.Exitf("CA certificate and key must be provided") |
| 146 | } |
| 147 | |
| 148 | p := newProdvider() |
| 149 | err := p.kubernetesConnect() |
| 150 | if err != nil { |
| 151 | glog.Exitf("Could not connect to kubernetes: %v", err) |
| 152 | } |
| 153 | creds := p.selfCreds() |
| 154 | |
| 155 | // Start serving gRPC |
| 156 | grpcLis, err := net.Listen("tcp", flagListenAddress) |
| 157 | if err != nil { |
| 158 | glog.Exitf("Could not listen for gRPC on %q: %v", flagListenAddress, err) |
| 159 | } |
| 160 | |
| 161 | glog.Infof("Starting gRPC on %q...", flagListenAddress) |
| 162 | grpcSrv := grpc.NewServer(creds) |
| 163 | |
| 164 | pb.RegisterProdviderServer(grpcSrv, p) |
| 165 | |
| 166 | go timebomb(grpcSrv) |
| 167 | |
| 168 | err = grpcSrv.Serve(grpcLis) |
| 169 | if err != nil { |
| 170 | glog.Exitf("Could not serve gRPC: %v", err) |
| 171 | } |
| 172 | } |