blob: 1b74d8b8bd4b9439a587efe8e64d664809d111d0 [file] [log] [blame]
giolekva4b2934b2021-10-08 19:37:12 +04001package main
2
3import (
giolekva4b2934b2021-10-08 19:37:12 +04004 "embed"
giolekvacc3ebcb2021-12-17 10:52:17 +04005 "encoding/base64"
giolekvab64297c2021-12-13 14:36:32 +04006 "encoding/json"
giolekva4b2934b2021-10-08 19:37:12 +04007 "flag"
8 "fmt"
9 "html/template"
giolekvacc3ebcb2021-12-17 10:52:17 +040010 "io/ioutil"
giolekva4b2934b2021-10-08 19:37:12 +040011 "log"
12 "net/http"
giolekvacc3ebcb2021-12-17 10:52:17 +040013 "time"
giolekva4b2934b2021-10-08 19:37:12 +040014
15 "github.com/gorilla/mux"
giolekvacc3ebcb2021-12-17 10:52:17 +040016 "sigs.k8s.io/yaml"
giolekva4b2934b2021-10-08 19:37:12 +040017
giolekva4b2934b2021-10-08 19:37:12 +040018 "k8s.io/client-go/kubernetes"
19 "k8s.io/client-go/tools/clientcmd"
20
giolekvac6859b02021-12-09 18:40:51 +040021 clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
giolekva4b2934b2021-10-08 19:37:12 +040022)
23
24var port = flag.Int("port", 8080, "Port to listen on.")
25var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
26var 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 +040027var namespace = flag.String("namespace", "", "Namespace where Nebula CA and Node secrets are stored.")
giolekvaf58a7692021-12-15 18:05:39 +040028var caName = flag.String("ca-name", "", "Name of the Nebula CA.")
giolekvacc3ebcb2021-12-17 10:52:17 +040029var configTmpl = flag.String("config-tmpl", "", "Path to the lighthouse configuration template file.")
giolekva4b2934b2021-10-08 19:37:12 +040030
31//go:embed templates/*
32var tmpls embed.FS
33
34type Templates struct {
35 Index *template.Template
36}
37
38func ParseTemplates(fs embed.FS) (*Templates, error) {
39 index, err := template.ParseFS(fs, "templates/index.html")
40 if err != nil {
41 return nil, err
42 }
43 return &Templates{index}, nil
44}
45
giolekva4b2934b2021-10-08 19:37:12 +040046type Handler struct {
47 mgr Manager
48 tmpls *Templates
49}
50
51func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
52 cas, err := h.mgr.ListAll()
53 if err != nil {
54 http.Error(w, err.Error(), http.StatusInternalServerError)
55 return
56 }
57 if err := h.tmpls.Index.Execute(w, cas); err != nil {
58 http.Error(w, err.Error(), http.StatusInternalServerError)
59 }
60}
61
62func (h *Handler) handleNode(w http.ResponseWriter, r *http.Request) {
63 vars := mux.Vars(r)
64 namespace := vars["namespace"]
65 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040066 qr, err := h.mgr.GetNodeCertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040067 if err != nil {
68 http.Error(w, err.Error(), http.StatusInternalServerError)
69 }
70 w.Header().Set("Content-Type", "img/png")
71 w.Write(qr)
72}
73
74func (h *Handler) handleCA(w http.ResponseWriter, r *http.Request) {
75 vars := mux.Vars(r)
76 namespace := vars["namespace"]
77 name := vars["name"]
giolekvab64297c2021-12-13 14:36:32 +040078 qr, err := h.mgr.GetCACertQR(namespace, name)
giolekva4b2934b2021-10-08 19:37:12 +040079 if err != nil {
80 http.Error(w, err.Error(), http.StatusInternalServerError)
81 }
82 w.Header().Set("Content-Type", "img/png")
83 w.Write(qr)
84}
85
86func (h *Handler) handleSignNode(w http.ResponseWriter, r *http.Request) {
87 if err := r.ParseForm(); err != nil {
88 http.Error(w, err.Error(), http.StatusBadRequest)
89 return
90 }
giolekvab64297c2021-12-13 14:36:32 +040091 _, _, err := h.mgr.CreateNode(
giolekva4b2934b2021-10-08 19:37:12 +040092 r.FormValue("node-namespace"),
93 r.FormValue("node-name"),
94 r.FormValue("ca-namespace"),
95 r.FormValue("ca-name"),
96 r.FormValue("ip-cidr"),
97 r.FormValue("pub-key"),
98 )
99 if err != nil {
100 http.Error(w, err.Error(), http.StatusInternalServerError)
101 return
102 }
103 http.Redirect(w, r, "/", http.StatusSeeOther)
104}
105
giolekvab64297c2021-12-13 14:36:32 +0400106func (h *Handler) getNextIP(w http.ResponseWriter, r *http.Request) {
107 ip, err := h.mgr.getNextIP()
108 if err != nil {
109 http.Error(w, err.Error(), http.StatusInternalServerError)
110 return
111 }
112 fmt.Fprint(w, ip)
113}
114
115type signReq struct {
116 Message []byte `json:"message"`
117}
118
119type signResp struct {
120 Signature []byte `json:"signature"`
121}
122
123func (h *Handler) sign(w http.ResponseWriter, r *http.Request) {
124 var req signReq
125 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
126 http.Error(w, err.Error(), http.StatusBadRequest)
127 return
128 }
129 signature, err := h.mgr.Sign(req.Message)
130 if err != nil {
131 http.Error(w, err.Error(), http.StatusInternalServerError)
132 return
133 }
134 w.Header().Set("Content-Type", "application/json")
135 resp := signResp{
136 signature,
137 }
138 if err := json.NewEncoder(w).Encode(resp); err != nil {
139 http.Error(w, err.Error(), http.StatusInternalServerError)
140 return
141 }
142}
143
giolekvaf58a7692021-12-15 18:05:39 +0400144type joinReq struct {
giolekvab64297c2021-12-13 14:36:32 +0400145 Message []byte `json:"message"`
146 Signature []byte `json:"signature"`
giolekvaf58a7692021-12-15 18:05:39 +0400147 Name string `json:"name"`
148 PublicKey []byte `json:"public_key"`
149 IPCidr string `json:"ip_cidr"`
giolekvab64297c2021-12-13 14:36:32 +0400150}
151
giolekvaf58a7692021-12-15 18:05:39 +0400152type joinResp struct {
153}
154
155func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
156 var req joinReq
giolekvab64297c2021-12-13 14:36:32 +0400157 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
158 http.Error(w, err.Error(), http.StatusBadRequest)
159 return
160 }
161 valid, err := h.mgr.VerifySignature(req.Message, req.Signature)
162 if err != nil {
163 http.Error(w, err.Error(), http.StatusInternalServerError)
164 return
165 }
166 if !valid {
167 http.Error(w, "Signature could not be verified", http.StatusBadRequest)
168 return
169 }
giolekvaf58a7692021-12-15 18:05:39 +0400170 _, _, err = h.mgr.CreateNode(
171 *namespace,
172 req.Name,
173 *namespace,
174 *caName,
175 req.IPCidr,
176 string(req.PublicKey),
177 )
178 if err != nil {
179 http.Error(w, err.Error(), http.StatusInternalServerError)
180 return
181 }
giolekvacc3ebcb2021-12-17 10:52:17 +0400182 for {
183 time.Sleep(1 * time.Second)
184 cfg, err := h.mgr.GetNodeConfig(*namespace, req.Name)
185 if err != nil {
186 fmt.Println(err.Error())
187 continue
188 }
189 cfgBytes, err := yaml.Marshal(cfg)
190 if err != nil {
191 http.Error(w, err.Error(), http.StatusInternalServerError)
192 return
193 }
194 cfgB64 := base64.StdEncoding.EncodeToString(cfgBytes)
195 if _, err := fmt.Fprint(w, cfgB64); err != nil {
196 http.Error(w, err.Error(), http.StatusInternalServerError)
197 return
198 }
199 break
200 }
201}
202
203func loadConfigTemplate(path string) (map[string]interface{}, error) {
204 tmpl, err := ioutil.ReadFile(path)
205 if err != nil {
206 return nil, err
207 }
208 var m map[string]interface{}
209 if err := yaml.Unmarshal(tmpl, &m); err != nil {
210 return nil, err
211 }
212 return m, nil
giolekvab64297c2021-12-13 14:36:32 +0400213}
214
giolekva4b2934b2021-10-08 19:37:12 +0400215func main() {
216 flag.Parse()
giolekvacc3ebcb2021-12-17 10:52:17 +0400217 cfgTmpl, err := loadConfigTemplate(*configTmpl)
218 if err != nil {
219 panic(err)
220 }
giolekva4b2934b2021-10-08 19:37:12 +0400221 cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
222 if err != nil {
223 panic(err)
224 }
225 kubeClient, err := kubernetes.NewForConfig(cfg)
226 if err != nil {
227 panic(err)
228 }
229 nebulaClient := clientset.NewForConfigOrDie(cfg)
230 t, err := ParseTemplates(tmpls)
231 if err != nil {
232 log.Fatal(err)
233 }
234 mgr := Manager{
235 kubeClient: kubeClient,
236 nebulaClient: nebulaClient,
giolekvab64297c2021-12-13 14:36:32 +0400237 namespace: *namespace,
giolekvaf58a7692021-12-15 18:05:39 +0400238 caName: *caName,
giolekvacc3ebcb2021-12-17 10:52:17 +0400239 cfgTmpl: cfgTmpl,
giolekva4b2934b2021-10-08 19:37:12 +0400240 }
241 handler := Handler{
242 mgr: mgr,
243 tmpls: t,
244 }
245 r := mux.NewRouter()
giolekvab64297c2021-12-13 14:36:32 +0400246 r.HandleFunc("/api/ip", handler.getNextIP)
247 r.HandleFunc("/api/sign", handler.sign)
giolekvaf58a7692021-12-15 18:05:39 +0400248 r.HandleFunc("/api/join", handler.join)
giolekva4b2934b2021-10-08 19:37:12 +0400249 r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
250 r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
251 r.HandleFunc("/sign-node", handler.handleSignNode)
252 r.HandleFunc("/", handler.handleIndex)
253 http.Handle("/", r)
254 fmt.Printf("Starting HTTP server on port: %d\n", *port)
255 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
256}