blob: 8738cd4cce8c42dabf853d1caadb6401d8e1a5d9 [file] [log] [blame]
giolekva4b2934b2021-10-08 19:37:12 +04001package main
2
3import (
4 "context"
5 "embed"
6 "flag"
7 "fmt"
8 "html/template"
9 "log"
10 "net/http"
11
12 "github.com/gorilla/mux"
13
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 "k8s.io/client-go/kubernetes"
16 "k8s.io/client-go/tools/clientcmd"
17
18 nebulav1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
19 clientset "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned"
20)
21
22var port = flag.Int("port", 8080, "Port to listen on.")
23var kubeConfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
24var masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
25
26//go:embed templates/*
27var tmpls embed.FS
28
29type Templates struct {
30 Index *template.Template
31}
32
33func ParseTemplates(fs embed.FS) (*Templates, error) {
34 index, err := template.ParseFS(fs, "templates/index.html")
35 if err != nil {
36 return nil, err
37 }
38 return &Templates{index}, nil
39}
40
41type nebulaCA struct {
42 Name string
43 Namespace string
44 Nodes []nebulaNode
45}
46
47type nebulaNode struct {
48 Name string
49 Namespace string
50 IP string
51}
52
53type Manager struct {
54 kubeClient kubernetes.Interface
55 nebulaClient clientset.Interface
56}
57
58func (m *Manager) ListAll() ([]*nebulaCA, error) {
59 ret := make([]*nebulaCA, 0)
60 cas, err := m.nebulaClient.LekvaV1().NebulaCAs("").List(context.TODO(), metav1.ListOptions{})
61 if err != nil {
62 return nil, err
63 }
64 for _, ca := range cas.Items {
65 ret = append(ret, &nebulaCA{
66 Name: ca.Name,
67 Namespace: ca.Namespace,
68 Nodes: make([]nebulaNode, 0),
69 })
70 }
71 nodes, err := m.nebulaClient.LekvaV1().NebulaNodes("").List(context.TODO(), metav1.ListOptions{})
72 if err != nil {
73 return nil, err
74 }
75 for _, node := range nodes.Items {
76 for _, ca := range ret {
77 if ca.Name == node.Spec.CAName {
78 ca.Nodes = append(ca.Nodes, nebulaNode{
79 Name: node.Name,
80 Namespace: node.Namespace,
81 IP: node.Spec.IPCidr,
82 })
83 }
84 }
85 }
86 return ret, nil
87}
88
89func (m *Manager) createNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
90 node := &nebulav1.NebulaNode{
91 ObjectMeta: metav1.ObjectMeta{
92 Name: name,
93 Namespace: namespace,
94 },
95 Spec: nebulav1.NebulaNodeSpec{
96 CAName: caName,
97 CANamespace: caNamespace,
98 IPCidr: ipCidr,
99 PubKey: pubKey,
100 SecretName: fmt.Sprintf("%s-cert", name),
101 },
102 }
103 node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
104 if err != nil {
105 return "", "", err
106 }
107 return node.Namespace, node.Name, nil
108}
109
110func (m *Manager) getNodeCertQR(namespace, name string) ([]byte, error) {
111 node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
112 if err != nil {
113 return nil, err
114 }
115 secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
116 if err != nil {
117 return nil, err
118 }
119 return secret.Data["host.png"], nil
120}
121
122func (m *Manager) getCACertQR(namespace, name string) ([]byte, error) {
123 ca, err := m.nebulaClient.LekvaV1().NebulaCAs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
124 if err != nil {
125 return nil, err
126 }
127 secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), ca.Spec.SecretName, metav1.GetOptions{})
128 if err != nil {
129 return nil, err
130 }
131 return secret.Data["ca.png"], nil
132}
133
134type Handler struct {
135 mgr Manager
136 tmpls *Templates
137}
138
139func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
140 cas, err := h.mgr.ListAll()
141 if err != nil {
142 http.Error(w, err.Error(), http.StatusInternalServerError)
143 return
144 }
145 if err := h.tmpls.Index.Execute(w, cas); err != nil {
146 http.Error(w, err.Error(), http.StatusInternalServerError)
147 }
148}
149
150func (h *Handler) handleNode(w http.ResponseWriter, r *http.Request) {
151 vars := mux.Vars(r)
152 namespace := vars["namespace"]
153 name := vars["name"]
154 qr, err := h.mgr.getNodeCertQR(namespace, name)
155 if err != nil {
156 http.Error(w, err.Error(), http.StatusInternalServerError)
157 }
158 w.Header().Set("Content-Type", "img/png")
159 w.Write(qr)
160}
161
162func (h *Handler) handleCA(w http.ResponseWriter, r *http.Request) {
163 vars := mux.Vars(r)
164 namespace := vars["namespace"]
165 name := vars["name"]
166 qr, err := h.mgr.getCACertQR(namespace, name)
167 if err != nil {
168 http.Error(w, err.Error(), http.StatusInternalServerError)
169 }
170 w.Header().Set("Content-Type", "img/png")
171 w.Write(qr)
172}
173
174func (h *Handler) handleSignNode(w http.ResponseWriter, r *http.Request) {
175 if err := r.ParseForm(); err != nil {
176 http.Error(w, err.Error(), http.StatusBadRequest)
177 return
178 }
179 _, _, err := h.mgr.createNode(
180 r.FormValue("node-namespace"),
181 r.FormValue("node-name"),
182 r.FormValue("ca-namespace"),
183 r.FormValue("ca-name"),
184 r.FormValue("ip-cidr"),
185 r.FormValue("pub-key"),
186 )
187 if err != nil {
188 http.Error(w, err.Error(), http.StatusInternalServerError)
189 return
190 }
191 http.Redirect(w, r, "/", http.StatusSeeOther)
192}
193
194func main() {
195 flag.Parse()
196 cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeConfig)
197 if err != nil {
198 panic(err)
199 }
200 kubeClient, err := kubernetes.NewForConfig(cfg)
201 if err != nil {
202 panic(err)
203 }
204 nebulaClient := clientset.NewForConfigOrDie(cfg)
205 t, err := ParseTemplates(tmpls)
206 if err != nil {
207 log.Fatal(err)
208 }
209 mgr := Manager{
210 kubeClient: kubeClient,
211 nebulaClient: nebulaClient,
212 }
213 handler := Handler{
214 mgr: mgr,
215 tmpls: t,
216 }
217 r := mux.NewRouter()
218 r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
219 r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
220 r.HandleFunc("/sign-node", handler.handleSignNode)
221 r.HandleFunc("/", handler.handleIndex)
222 http.Handle("/", r)
223 fmt.Printf("Starting HTTP server on port: %d\n", *port)
224 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
225}