blob: 906a6742779ffaad6d3bf971b93b64023d315b0d [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"
18 "net/http"
giolekvacc3ebcb2021-12-17 10:52:17 +040019 "time"
giolekva4b2934b2021-10-08 19:37:12 +040020
21 "github.com/gorilla/mux"
giolekvacc3ebcb2021-12-17 10:52:17 +040022 "sigs.k8s.io/yaml"
giolekva4b2934b2021-10-08 19:37:12 +040023
giolekva4b2934b2021-10-08 19:37:12 +040024 "k8s.io/client-go/kubernetes"
25 "k8s.io/client-go/tools/clientcmd"
26
giolekvac6859b02021-12-09 18:40:51 +040027 clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
giolekva4b2934b2021-10-08 19:37:12 +040028)
29
30var port = flag.Int("port", 8080, "Port to listen on.")
31var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
32var 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 +040033var namespace = flag.String("namespace", "", "Namespace where Nebula CA and Node secrets are stored.")
giolekvaf58a7692021-12-15 18:05:39 +040034var caName = flag.String("ca-name", "", "Name of the Nebula CA.")
giolekvacc3ebcb2021-12-17 10:52:17 +040035var configTmpl = flag.String("config-tmpl", "", "Path to the lighthouse configuration template file.")
giolekva4b2934b2021-10-08 19:37:12 +040036
37//go:embed templates/*
38var tmpls embed.FS
39
40type Templates struct {
41 Index *template.Template
42}
43
44func ParseTemplates(fs embed.FS) (*Templates, error) {
45 index, err := template.ParseFS(fs, "templates/index.html")
46 if err != nil {
47 return nil, err
48 }
49 return &Templates{index}, nil
50}
51
giolekva4b2934b2021-10-08 19:37:12 +040052type Handler struct {
53 mgr Manager
54 tmpls *Templates
55}
56
57func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
58 cas, err := h.mgr.ListAll()
59 if err != nil {
60 http.Error(w, err.Error(), http.StatusInternalServerError)
61 return
62 }
63 if err := h.tmpls.Index.Execute(w, cas); err != nil {
64 http.Error(w, err.Error(), http.StatusInternalServerError)
65 }
66}
67
68func (h *Handler) handleNode(w http.ResponseWriter, r *http.Request) {
69 vars := mux.Vars(r)
70 namespace := vars["namespace"]
71 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040072 qr, err := h.mgr.GetNodeCertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040073 if err != nil {
74 http.Error(w, err.Error(), http.StatusInternalServerError)
75 }
76 w.Header().Set("Content-Type", "img/png")
77 w.Write(qr)
78}
79
80func (h *Handler) handleCA(w http.ResponseWriter, r *http.Request) {
81 vars := mux.Vars(r)
82 namespace := vars["namespace"]
83 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040084 qr, err := h.mgr.GetCACertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040085 if err != nil {
86 http.Error(w, err.Error(), http.StatusInternalServerError)
87 }
88 w.Header().Set("Content-Type", "img/png")
89 w.Write(qr)
90}
91
giolekvab64297c2021-12-13 14:36:32 +040092type signReq struct {
93 Message []byte `json:"message"`
94}
95
96type signResp struct {
97 Signature []byte `json:"signature"`
98}
99
100func (h *Handler) sign(w http.ResponseWriter, r *http.Request) {
101 var req signReq
102 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
103 http.Error(w, err.Error(), http.StatusBadRequest)
104 return
105 }
106 signature, err := h.mgr.Sign(req.Message)
107 if err != nil {
108 http.Error(w, err.Error(), http.StatusInternalServerError)
109 return
110 }
111 w.Header().Set("Content-Type", "application/json")
112 resp := signResp{
113 signature,
114 }
115 if err := json.NewEncoder(w).Encode(resp); err != nil {
116 http.Error(w, err.Error(), http.StatusInternalServerError)
117 return
118 }
119}
120
giolekvaf58a7692021-12-15 18:05:39 +0400121type joinReq struct {
giolekvab64297c2021-12-13 14:36:32 +0400122 Message []byte `json:"message"`
123 Signature []byte `json:"signature"`
giolekvaf58a7692021-12-15 18:05:39 +0400124 Name string `json:"name"`
125 PublicKey []byte `json:"public_key"`
126 IPCidr string `json:"ip_cidr"`
giolekvab64297c2021-12-13 14:36:32 +0400127}
128
giolekvaf58a7692021-12-15 18:05:39 +0400129func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
130 var req joinReq
giolekvab64297c2021-12-13 14:36:32 +0400131 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
132 http.Error(w, err.Error(), http.StatusBadRequest)
133 return
134 }
135 valid, err := h.mgr.VerifySignature(req.Message, req.Signature)
136 if err != nil {
137 http.Error(w, err.Error(), http.StatusInternalServerError)
138 return
139 }
140 if !valid {
141 http.Error(w, "Signature could not be verified", http.StatusBadRequest)
142 return
143 }
giolekvaf58a7692021-12-15 18:05:39 +0400144 _, _, err = h.mgr.CreateNode(
145 *namespace,
146 req.Name,
147 *namespace,
148 *caName,
149 req.IPCidr,
150 string(req.PublicKey),
giolekva3f0dcda2021-12-22 23:32:49 +0400151 nil,
giolekvaf58a7692021-12-15 18:05:39 +0400152 )
153 if err != nil {
154 http.Error(w, err.Error(), http.StatusInternalServerError)
155 return
156 }
giolekvacc3ebcb2021-12-17 10:52:17 +0400157 for {
158 time.Sleep(1 * time.Second)
159 cfg, err := h.mgr.GetNodeConfig(*namespace, req.Name)
160 if err != nil {
161 fmt.Println(err.Error())
162 continue
163 }
164 cfgBytes, err := yaml.Marshal(cfg)
165 if err != nil {
166 http.Error(w, err.Error(), http.StatusInternalServerError)
167 return
168 }
169 cfgB64 := base64.StdEncoding.EncodeToString(cfgBytes)
170 if _, err := fmt.Fprint(w, cfgB64); err != nil {
171 http.Error(w, err.Error(), http.StatusInternalServerError)
172 return
173 }
174 break
175 }
176}
177
giolekva3f0dcda2021-12-22 23:32:49 +0400178type getResp struct {
179 Key []byte `json:"key"`
180 Nonce []byte `json:"nonce"`
181 Data []byte `json:"data"`
182}
183
184func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
185 fmt.Println("##### GET")
186 vars := mux.Vars(r)
187 pubKey, err := h.mgr.GetNodeEncryptionPublicKey(*namespace, vars["name"])
188 if err != nil {
189 http.Error(w, err.Error(), http.StatusInternalServerError)
190 return
191 }
192 fmt.Println("Got key")
193 key := make([]byte, 32)
194 if _, err := io.ReadFull(rand.Reader, key); err != nil {
195 http.Error(w, err.Error(), http.StatusInternalServerError)
196 return
197 }
198 block, err := aes.NewCipher(key)
199 if err != nil {
200 http.Error(w, err.Error(), http.StatusInternalServerError)
201 return
202 }
203 aesgcm, err := cipher.NewGCM(block)
204 if err != nil {
205 http.Error(w, err.Error(), http.StatusInternalServerError)
206 return
207 }
208 nonce := make([]byte, 12)
209 if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
210 panic(err.Error())
211 }
212 for {
213 time.Sleep(1 * time.Second)
214 cfg, err := h.mgr.GetNodeConfig(*namespace, vars["name"])
215 if err != nil {
216 fmt.Println(err.Error())
217 continue
218 }
219 cfgBytes, err := yaml.Marshal(cfg)
220 if err != nil {
221 http.Error(w, err.Error(), http.StatusInternalServerError)
222 return
223 }
224 cfgEnc := aesgcm.Seal(nil, nonce, cfgBytes, nil)
225 keyEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, key, []byte(""))
226 if err != nil {
227 http.Error(w, err.Error(), http.StatusInternalServerError)
228 return
229 }
230 nonceEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, nonce, []byte(""))
231 if err != nil {
232 http.Error(w, err.Error(), http.StatusInternalServerError)
233 return
234 }
235 w.Header().Set("Content-Type", "application/json")
236 resp := getResp{
237 Key: keyEnc,
238 Nonce: nonceEnc,
239 Data: cfgEnc,
240 }
241 if err := json.NewEncoder(w).Encode(&resp); err != nil {
242 http.Error(w, err.Error(), http.StatusInternalServerError)
243 return
244 }
245 break
246 }
247}
248
249type approveReq struct {
250 EncPublicKey []byte `json:"enc_public_key"`
251 Name string `json:"name"`
252 NetPublicKey []byte `json:"net_public_key"`
253 IPCidr string `json:"ip_cidr"`
254}
255
256type approveResp struct {
257}
258
259func (h *Handler) approve(w http.ResponseWriter, r *http.Request) {
260 var req approveReq
261 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
262 http.Error(w, err.Error(), http.StatusBadRequest)
263 return
264 }
265 fmt.Println("---- APPROVE")
266 fmt.Printf("%#v\n", req)
267 _, _, err := h.mgr.CreateNode(
268 *namespace,
269 req.Name,
270 *namespace,
271 *caName,
272 req.IPCidr,
273 string(req.NetPublicKey),
274 req.EncPublicKey,
275 )
276 if err != nil {
277 fmt.Println(err.Error())
278 http.Error(w, err.Error(), http.StatusInternalServerError)
279 return
280 }
281}
282
giolekvacc3ebcb2021-12-17 10:52:17 +0400283func loadConfigTemplate(path string) (map[string]interface{}, error) {
284 tmpl, err := ioutil.ReadFile(path)
285 if err != nil {
286 return nil, err
287 }
288 var m map[string]interface{}
289 if err := yaml.Unmarshal(tmpl, &m); err != nil {
290 return nil, err
291 }
292 return m, nil
giolekvab64297c2021-12-13 14:36:32 +0400293}
294
giolekva4b2934b2021-10-08 19:37:12 +0400295func main() {
296 flag.Parse()
giolekvacc3ebcb2021-12-17 10:52:17 +0400297 cfgTmpl, err := loadConfigTemplate(*configTmpl)
298 if err != nil {
299 panic(err)
300 }
giolekva4b2934b2021-10-08 19:37:12 +0400301 cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
302 if err != nil {
303 panic(err)
304 }
305 kubeClient, err := kubernetes.NewForConfig(cfg)
306 if err != nil {
307 panic(err)
308 }
309 nebulaClient := clientset.NewForConfigOrDie(cfg)
310 t, err := ParseTemplates(tmpls)
311 if err != nil {
312 log.Fatal(err)
313 }
314 mgr := Manager{
315 kubeClient: kubeClient,
316 nebulaClient: nebulaClient,
giolekvab64297c2021-12-13 14:36:32 +0400317 namespace: *namespace,
giolekvaf58a7692021-12-15 18:05:39 +0400318 caName: *caName,
giolekvacc3ebcb2021-12-17 10:52:17 +0400319 cfgTmpl: cfgTmpl,
giolekva4b2934b2021-10-08 19:37:12 +0400320 }
321 handler := Handler{
322 mgr: mgr,
323 tmpls: t,
324 }
325 r := mux.NewRouter()
giolekvab64297c2021-12-13 14:36:32 +0400326 r.HandleFunc("/api/sign", handler.sign)
giolekvaf58a7692021-12-15 18:05:39 +0400327 r.HandleFunc("/api/join", handler.join)
giolekva3f0dcda2021-12-22 23:32:49 +0400328 r.HandleFunc("/api/approve", handler.approve)
329 r.HandleFunc("/api/get/{name:[a-zA-z0-9-]+}", handler.get)
giolekva4b2934b2021-10-08 19:37:12 +0400330 r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
331 r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
giolekva4b2934b2021-10-08 19:37:12 +0400332 r.HandleFunc("/", handler.handleIndex)
333 http.Handle("/", r)
334 fmt.Printf("Starting HTTP server on port: %d\n", *port)
335 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
336}