blob: 91276309611c0ffedded9c320fd6658997714f72 [file] [log] [blame]
giolekva4b2934b2021-10-08 19:37:12 +04001package main
2
3import (
giolekva3f0dcda2021-12-22 23:32:49 +04004 "crypto/aes"
5 "crypto/cipher"
6 "crypto/rand"
7 "crypto/rsa"
8 "crypto/sha256"
giolekva4b2934b2021-10-08 19:37:12 +04009 "embed"
giolekvacc3ebcb2021-12-17 10:52:17 +040010 "encoding/base64"
giolekvab64297c2021-12-13 14:36:32 +040011 "encoding/json"
giolekva4b2934b2021-10-08 19:37:12 +040012 "flag"
13 "fmt"
14 "html/template"
giolekva3f0dcda2021-12-22 23:32:49 +040015 "io"
giolekvacc3ebcb2021-12-17 10:52:17 +040016 "io/ioutil"
giolekva4b2934b2021-10-08 19:37:12 +040017 "log"
giolekva6bb21c22021-12-29 21:31:08 +040018 "net"
giolekva4b2934b2021-10-08 19:37:12 +040019 "net/http"
giolekvacc3ebcb2021-12-17 10:52:17 +040020 "time"
giolekva4b2934b2021-10-08 19:37:12 +040021
22 "github.com/gorilla/mux"
giolekvacc3ebcb2021-12-17 10:52:17 +040023 "sigs.k8s.io/yaml"
giolekva4b2934b2021-10-08 19:37:12 +040024
giolekva4b2934b2021-10-08 19:37:12 +040025 "k8s.io/client-go/kubernetes"
26 "k8s.io/client-go/tools/clientcmd"
27
giolekvac6859b02021-12-09 18:40:51 +040028 clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
giolekva4b2934b2021-10-08 19:37:12 +040029)
30
31var port = flag.Int("port", 8080, "Port to listen on.")
32var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
33var masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
giolekvab64297c2021-12-13 14:36:32 +040034var namespace = flag.String("namespace", "", "Namespace where Nebula CA and Node secrets are stored.")
giolekvaf58a7692021-12-15 18:05:39 +040035var caName = flag.String("ca-name", "", "Name of the Nebula CA.")
giolekvacc3ebcb2021-12-17 10:52:17 +040036var configTmpl = flag.String("config-tmpl", "", "Path to the lighthouse configuration template file.")
giolekva4b2934b2021-10-08 19:37:12 +040037
38//go:embed templates/*
39var tmpls embed.FS
40
41type Templates struct {
42 Index *template.Template
43}
44
45func ParseTemplates(fs embed.FS) (*Templates, error) {
46 index, err := template.ParseFS(fs, "templates/index.html")
47 if err != nil {
48 return nil, err
49 }
50 return &Templates{index}, nil
51}
52
giolekva4b2934b2021-10-08 19:37:12 +040053type Handler struct {
54 mgr Manager
55 tmpls *Templates
56}
57
58func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
59 cas, err := h.mgr.ListAll()
60 if err != nil {
61 http.Error(w, err.Error(), http.StatusInternalServerError)
62 return
63 }
64 if err := h.tmpls.Index.Execute(w, cas); err != nil {
65 http.Error(w, err.Error(), http.StatusInternalServerError)
66 }
67}
68
69func (h *Handler) handleNode(w http.ResponseWriter, r *http.Request) {
70 vars := mux.Vars(r)
71 namespace := vars["namespace"]
72 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040073 qr, err := h.mgr.GetNodeCertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040074 if err != nil {
75 http.Error(w, err.Error(), http.StatusInternalServerError)
76 }
77 w.Header().Set("Content-Type", "img/png")
78 w.Write(qr)
79}
80
81func (h *Handler) handleCA(w http.ResponseWriter, r *http.Request) {
82 vars := mux.Vars(r)
83 namespace := vars["namespace"]
84 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040085 qr, err := h.mgr.GetCACertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040086 if err != nil {
87 http.Error(w, err.Error(), http.StatusInternalServerError)
88 }
89 w.Header().Set("Content-Type", "img/png")
90 w.Write(qr)
91}
92
giolekvab64297c2021-12-13 14:36:32 +040093type signReq struct {
94 Message []byte `json:"message"`
95}
96
97type signResp struct {
98 Signature []byte `json:"signature"`
99}
100
101func (h *Handler) sign(w http.ResponseWriter, r *http.Request) {
102 var req signReq
103 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
104 http.Error(w, err.Error(), http.StatusBadRequest)
105 return
106 }
107 signature, err := h.mgr.Sign(req.Message)
108 if err != nil {
109 http.Error(w, err.Error(), http.StatusInternalServerError)
110 return
111 }
112 w.Header().Set("Content-Type", "application/json")
113 resp := signResp{
114 signature,
115 }
116 if err := json.NewEncoder(w).Encode(resp); err != nil {
117 http.Error(w, err.Error(), http.StatusInternalServerError)
118 return
119 }
120}
121
giolekvaf58a7692021-12-15 18:05:39 +0400122type joinReq struct {
giolekvab64297c2021-12-13 14:36:32 +0400123 Message []byte `json:"message"`
124 Signature []byte `json:"signature"`
giolekvaf58a7692021-12-15 18:05:39 +0400125 Name string `json:"name"`
126 PublicKey []byte `json:"public_key"`
127 IPCidr string `json:"ip_cidr"`
giolekvab64297c2021-12-13 14:36:32 +0400128}
129
giolekvaf58a7692021-12-15 18:05:39 +0400130func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
131 var req joinReq
giolekvab64297c2021-12-13 14:36:32 +0400132 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
133 http.Error(w, err.Error(), http.StatusBadRequest)
134 return
135 }
136 valid, err := h.mgr.VerifySignature(req.Message, req.Signature)
137 if err != nil {
138 http.Error(w, err.Error(), http.StatusInternalServerError)
139 return
140 }
141 if !valid {
142 http.Error(w, "Signature could not be verified", http.StatusBadRequest)
143 return
144 }
giolekvaf58a7692021-12-15 18:05:39 +0400145 _, _, err = h.mgr.CreateNode(
146 *namespace,
147 req.Name,
148 *namespace,
149 *caName,
150 req.IPCidr,
151 string(req.PublicKey),
giolekva3f0dcda2021-12-22 23:32:49 +0400152 nil,
giolekvaf58a7692021-12-15 18:05:39 +0400153 )
154 if err != nil {
155 http.Error(w, err.Error(), http.StatusInternalServerError)
156 return
157 }
giolekvacc3ebcb2021-12-17 10:52:17 +0400158 for {
159 time.Sleep(1 * time.Second)
160 cfg, err := h.mgr.GetNodeConfig(*namespace, req.Name)
161 if err != nil {
162 fmt.Println(err.Error())
163 continue
164 }
165 cfgBytes, err := yaml.Marshal(cfg)
166 if err != nil {
167 http.Error(w, err.Error(), http.StatusInternalServerError)
168 return
169 }
170 cfgB64 := base64.StdEncoding.EncodeToString(cfgBytes)
171 if _, err := fmt.Fprint(w, cfgB64); err != nil {
172 http.Error(w, err.Error(), http.StatusInternalServerError)
173 return
174 }
175 break
176 }
177}
178
giolekva3f0dcda2021-12-22 23:32:49 +0400179type getResp struct {
180 Key []byte `json:"key"`
181 Nonce []byte `json:"nonce"`
182 Data []byte `json:"data"`
183}
184
185func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
186 fmt.Println("##### GET")
187 vars := mux.Vars(r)
188 pubKey, err := h.mgr.GetNodeEncryptionPublicKey(*namespace, vars["name"])
189 if err != nil {
190 http.Error(w, err.Error(), http.StatusInternalServerError)
191 return
192 }
193 fmt.Println("Got key")
194 key := make([]byte, 32)
195 if _, err := io.ReadFull(rand.Reader, key); err != nil {
196 http.Error(w, err.Error(), http.StatusInternalServerError)
197 return
198 }
199 block, err := aes.NewCipher(key)
200 if err != nil {
201 http.Error(w, err.Error(), http.StatusInternalServerError)
202 return
203 }
204 aesgcm, err := cipher.NewGCM(block)
205 if err != nil {
206 http.Error(w, err.Error(), http.StatusInternalServerError)
207 return
208 }
209 nonce := make([]byte, 12)
210 if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
211 panic(err.Error())
212 }
213 for {
214 time.Sleep(1 * time.Second)
215 cfg, err := h.mgr.GetNodeConfig(*namespace, vars["name"])
216 if err != nil {
217 fmt.Println(err.Error())
218 continue
219 }
220 cfgBytes, err := yaml.Marshal(cfg)
221 if err != nil {
222 http.Error(w, err.Error(), http.StatusInternalServerError)
223 return
224 }
225 cfgEnc := aesgcm.Seal(nil, nonce, cfgBytes, nil)
226 keyEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, key, []byte(""))
227 if err != nil {
228 http.Error(w, err.Error(), http.StatusInternalServerError)
229 return
230 }
231 nonceEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, nonce, []byte(""))
232 if err != nil {
233 http.Error(w, err.Error(), http.StatusInternalServerError)
234 return
235 }
236 w.Header().Set("Content-Type", "application/json")
237 resp := getResp{
238 Key: keyEnc,
239 Nonce: nonceEnc,
240 Data: cfgEnc,
241 }
242 if err := json.NewEncoder(w).Encode(&resp); err != nil {
243 http.Error(w, err.Error(), http.StatusInternalServerError)
244 return
245 }
246 break
247 }
248}
249
250type approveReq struct {
251 EncPublicKey []byte `json:"enc_public_key"`
252 Name string `json:"name"`
253 NetPublicKey []byte `json:"net_public_key"`
254 IPCidr string `json:"ip_cidr"`
255}
256
257type approveResp struct {
258}
259
260func (h *Handler) approve(w http.ResponseWriter, r *http.Request) {
261 var req approveReq
262 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
263 http.Error(w, err.Error(), http.StatusBadRequest)
264 return
265 }
giolekva3f0dcda2021-12-22 23:32:49 +0400266 _, _, err := h.mgr.CreateNode(
267 *namespace,
268 req.Name,
269 *namespace,
270 *caName,
271 req.IPCidr,
272 string(req.NetPublicKey),
273 req.EncPublicKey,
274 )
275 if err != nil {
276 fmt.Println(err.Error())
277 http.Error(w, err.Error(), http.StatusInternalServerError)
278 return
279 }
280}
281
giolekva6bb21c22021-12-29 21:31:08 +0400282type processCAReq struct {
283 Name string `json:"name"`
284}
285
286func (h *Handler) processCA(w http.ResponseWriter, r *http.Request) {
287 var req processCAReq
288 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
289 http.Error(w, err.Error(), http.StatusBadRequest)
290 return
291 }
292 ca, err := CreateCertificateAuthority(req.Name)
293 if err != nil {
294 http.Error(w, err.Error(), http.StatusInternalServerError)
295 return
296 }
297 w.Header().Set("Content-Type", "application/json")
298 if err := json.NewEncoder(w).Encode(ca); err != nil {
299 http.Error(w, err.Error(), http.StatusInternalServerError)
300 return
301 }
302}
303
304type processNodeReq struct {
305 CAPrivateKey []byte `json:"ca_private_key"`
306 CACert []byte `json:"ca_certificate"`
307 NodeName string `json:"node_name"`
308 NodePublicKey []byte `json:"node_public_key,omitempty"`
309 NodeIPCidr string `json:"node_ip_cidr"`
310}
311
312func (h *Handler) processNode(w http.ResponseWriter, r *http.Request) {
313 var req processNodeReq
314 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
315 http.Error(w, err.Error(), http.StatusBadRequest)
316 return
317 }
318 _, ipNet, err := net.ParseCIDR(req.NodeIPCidr)
319 if err != nil {
320 http.Error(w, err.Error(), http.StatusBadRequest)
321 return
322 }
323 node, err := SignNebulaNode(req.CAPrivateKey, req.CACert, req.NodeName, req.NodePublicKey, ipNet)
324 if err != nil {
325 http.Error(w, err.Error(), http.StatusBadRequest)
326 return
327 }
328 w.Header().Set("Content-Type", "application/json")
329 if err := json.NewEncoder(w).Encode(node); err != nil {
330 http.Error(w, err.Error(), http.StatusInternalServerError)
331 return
332 }
333}
334
giolekvacc3ebcb2021-12-17 10:52:17 +0400335func loadConfigTemplate(path string) (map[string]interface{}, error) {
336 tmpl, err := ioutil.ReadFile(path)
337 if err != nil {
338 return nil, err
339 }
340 var m map[string]interface{}
341 if err := yaml.Unmarshal(tmpl, &m); err != nil {
342 return nil, err
343 }
344 return m, nil
giolekvab64297c2021-12-13 14:36:32 +0400345}
346
giolekva4b2934b2021-10-08 19:37:12 +0400347func main() {
348 flag.Parse()
giolekvacc3ebcb2021-12-17 10:52:17 +0400349 cfgTmpl, err := loadConfigTemplate(*configTmpl)
350 if err != nil {
351 panic(err)
352 }
giolekva4b2934b2021-10-08 19:37:12 +0400353 cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
354 if err != nil {
355 panic(err)
356 }
357 kubeClient, err := kubernetes.NewForConfig(cfg)
358 if err != nil {
359 panic(err)
360 }
361 nebulaClient := clientset.NewForConfigOrDie(cfg)
362 t, err := ParseTemplates(tmpls)
363 if err != nil {
364 log.Fatal(err)
365 }
366 mgr := Manager{
367 kubeClient: kubeClient,
368 nebulaClient: nebulaClient,
giolekvab64297c2021-12-13 14:36:32 +0400369 namespace: *namespace,
giolekvaf58a7692021-12-15 18:05:39 +0400370 caName: *caName,
giolekvacc3ebcb2021-12-17 10:52:17 +0400371 cfgTmpl: cfgTmpl,
giolekva4b2934b2021-10-08 19:37:12 +0400372 }
373 handler := Handler{
374 mgr: mgr,
375 tmpls: t,
376 }
377 r := mux.NewRouter()
giolekvab64297c2021-12-13 14:36:32 +0400378 r.HandleFunc("/api/sign", handler.sign)
giolekvaf58a7692021-12-15 18:05:39 +0400379 r.HandleFunc("/api/join", handler.join)
giolekva3f0dcda2021-12-22 23:32:49 +0400380 r.HandleFunc("/api/approve", handler.approve)
381 r.HandleFunc("/api/get/{name:[a-zA-z0-9-]+}", handler.get)
giolekva6bb21c22021-12-29 21:31:08 +0400382 r.HandleFunc("/api/process/authority", handler.processCA)
383 r.HandleFunc("/api/process/node", handler.processNode)
giolekva4b2934b2021-10-08 19:37:12 +0400384 r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
385 r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
giolekva4b2934b2021-10-08 19:37:12 +0400386 r.HandleFunc("/", handler.handleIndex)
387 http.Handle("/", r)
388 fmt.Printf("Starting HTTP server on port: %d\n", *port)
389 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
390}