blob: ca114f082fbe434530ffc136da190ae5ae223efc [file] [log] [blame]
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +02001package main
2
3import (
Serge Bazanski3a6d67e2023-03-31 22:36:27 +00004 "crypto/ed25519"
5 "crypto/x509"
6 "encoding/pem"
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +02007 "flag"
Serge Bazanski3a6d67e2023-03-31 22:36:27 +00008 "fmt"
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +02009 "math/rand"
10 "net"
11 "os"
12 "time"
13
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020014 "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
21var (
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
35func init() {
36 flag.Set("logtostderr", "true")
37}
38
39type prodvider struct {
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000040 k8s *kubernetes.Clientset
41 srv *grpc.Server
42
43 intermediateCAKey ed25519.PrivateKey
44 intermediateCACert *x509.Certificate
45 kubeCACert *x509.Certificate
46}
47
48func 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
64func 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 Bazanskib13b7ff2019-08-29 20:12:24 +020084}
85
86func newProdvider() *prodvider {
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000087 kubeCACert, err := loadCert(flagKubeCACertificatePath)
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020088 if err != nil {
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000089 glog.Exitf("Loading kube CA certificate failed: %v", err)
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020090 }
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000091 intermediateCACert, err := loadCert(flagCACertificatePath)
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020092 if err != nil {
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000093 glog.Exitf("Loading intermediate CAcertificate failed: %v", err)
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020094 }
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000095 intermediateCAKey, err := loadKey(flagCAKeyPath)
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +020096 return &prodvider{
Serge Bazanski3a6d67e2023-03-31 22:36:27 +000097 intermediateCAKey: intermediateCAKey,
98 intermediateCACert: intermediateCACert,
99 kubeCACert: kubeCACert,
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +0200100 }
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.
106func 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
130func 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}