blob: 50917d70273c15472494581d0ce6118ba61bd038 [file] [log] [blame]
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04001package main
2
3import (
giob36178f2024-08-23 18:59:15 +04004 "bytes"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04005 "encoding/json"
giob36178f2024-08-23 18:59:15 +04006 "errors"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04007 "flag"
8 "fmt"
giob36178f2024-08-23 18:59:15 +04009 "io"
10 "io/ioutil"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040011 "log"
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040012 "net"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040013 "net/http"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040014 "os"
gioc23530e2024-05-01 11:06:09 +040015 "strings"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040016 "text/template"
giob36178f2024-08-23 18:59:15 +040017 "time"
18
19 "golang.org/x/exp/rand"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040020
gio6ec78542024-06-12 11:21:18 +040021 "github.com/gorilla/mux"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040022)
23
24var port = flag.Int("port", 3000, "Port to listen on")
25var config = flag.String("config", "", "Path to headscale config")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040026var acls = flag.String("acls", "", "Path to the headscale acls file")
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040027var ipSubnet = flag.String("ip-subnet", "10.1.0.0/24", "IP subnet of the private network")
giob36178f2024-08-23 18:59:15 +040028var fetchUsersAddr = flag.String("fetch-users-addr", "", "API endpoint to fetch user data")
29var self = flag.String("self", "", "Self address")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040030
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040031// TODO(gio): make internal network cidr and proxy user configurable
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040032const defaultACLs = `
33{
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040034 "autoApprovers": {
35 "routes": {
gioc23530e2024-05-01 11:06:09 +040036 {{- range .cidrs }}
37 "{{ . }}": ["*"],
38 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040039 },
40 },
41 "acls": [
gioe10ba162025-07-31 19:52:29 +040042 {
43 "action": "accept",
44 "src": ["*"],
45 "dst": ["*:*"]
46 },
giof6ad2982024-08-23 17:42:49 +040047 // {
48 // "action": "accept",
49 // "src": ["10.42.0.0/16", "10.43.0.0/16", "135.181.48.180/32", "65.108.39.172/32"],
50 // "dst": ["10.42.0.0/16:*", "10.43.0.0/16:*", "135.181.48.180/32:*", "65.108.39.172/32:*"],
51 // },
gioc23530e2024-05-01 11:06:09 +040052 {{- range .cidrs }}
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040053 { // Everyone has passthough access to private-network-proxy node
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040054 "action": "accept",
55 "src": ["*"],
gioc23530e2024-05-01 11:06:09 +040056 "dst": ["{{ . }}:*", "private-network-proxy:0"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040057 },
gioc23530e2024-05-01 11:06:09 +040058 {{- end }}
giof6ad2982024-08-23 17:42:49 +040059 { // Everyone has access to every port of nodes owned by private-network-proxy
60 "action": "accept",
61 "src": ["*"],
62 "dst": ["private-network-proxy:*"],
63 },
64 {
65 "action": "accept",
66 "src": ["private-network-proxy"],
67 "dst": ["private-network-proxy:*"],
68 },
giob36178f2024-08-23 18:59:15 +040069 {{- range .users }}
giof6ad2982024-08-23 17:42:49 +040070 {
giob36178f2024-08-23 18:59:15 +040071 "action": "accept",
72 "src": ["{{ . }}"],
73 "dst": ["{{ . }}:*"],
74 },
75 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040076 ],
77}
78`
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040079
80type server struct {
giob36178f2024-08-23 18:59:15 +040081 port int
82 client *client
83 fetchUsersAddr string
84 self string
85 aclsPath string
86 aclsReloadPath string
87 cidrs []string
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040088}
89
giob36178f2024-08-23 18:59:15 +040090func newServer(port int, client *client, fetchUsersAddr, self, aclsPath string, cidrs []string) *server {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040091 return &server{
92 port,
93 client,
giob36178f2024-08-23 18:59:15 +040094 fetchUsersAddr,
95 self,
96 aclsPath,
97 fmt.Sprintf("%s-reload", aclsPath), // TODO(gio): take from the flag
98 cidrs,
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040099 }
100}
101
gio6ec78542024-06-12 11:21:18 +0400102func (s *server) start() error {
giob36178f2024-08-23 18:59:15 +0400103 f, err := os.Create(s.aclsReloadPath)
104 if err != nil {
105 return err
106 }
107 f.Close()
gio6ec78542024-06-12 11:21:18 +0400108 r := mux.NewRouter()
giob36178f2024-08-23 18:59:15 +0400109 r.HandleFunc("/sync-users", s.handleSyncUsers).Methods(http.MethodGet)
gio6ec78542024-06-12 11:21:18 +0400110 r.HandleFunc("/user/{user}/preauthkey", s.createReusablePreAuthKey).Methods(http.MethodPost)
gio864b4332024-09-05 13:56:47 +0400111 r.HandleFunc("/user/{user}/preauthkey", s.expireReusablePreAuthKey).Methods(http.MethodDelete)
112 r.HandleFunc("/user/{user}/node/{node}/expire", s.expireUserNode).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400113 r.HandleFunc("/user/{user}/node/{node}/ip", s.getNodeIP).Methods(http.MethodGet)
gio864b4332024-09-05 13:56:47 +0400114 r.HandleFunc("/user/{user}/node/{node}", s.removeUserNode).Methods(http.MethodDelete)
gio43e0aad2025-08-01 16:17:27 +0400115 r.HandleFunc("/user/{user}/node", s.getUserNodes).Methods(http.MethodGet)
gio6ec78542024-06-12 11:21:18 +0400116 r.HandleFunc("/user", s.createUser).Methods(http.MethodPost)
117 r.HandleFunc("/routes/{id}/enable", s.enableRoute).Methods(http.MethodPost)
gio6439d442025-08-03 06:18:15 +0400118 r.HandleFunc("/cmd", s.runCmd).Methods(http.MethodPost)
giob36178f2024-08-23 18:59:15 +0400119 go func() {
120 rand.Seed(uint64(time.Now().UnixNano()))
121 s.syncUsers()
122 for {
123 delay := time.Duration(rand.Intn(60)+60) * time.Second
124 time.Sleep(delay)
125 s.syncUsers()
126 }
127 }()
gio6ec78542024-06-12 11:21:18 +0400128 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400129}
130
gio6439d442025-08-03 06:18:15 +0400131type cmdReq struct {
132 Cmd string `json:"cmd"`
133}
134
135func (s *server) runCmd(w http.ResponseWriter, r *http.Request) {
136 var req cmdReq
137 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
138 http.Error(w, err.Error(), http.StatusBadRequest)
139 return
140 }
141 if out, err := s.client.run(strings.Split(req.Cmd, " ")...); err != nil && !errors.Is(err, ErrorAlreadyExists) {
142 http.Error(w, err.Error(), http.StatusInternalServerError)
143 return
144 } else {
145 fmt.Fprint(w, out)
146 }
147}
148
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400149type createUserReq struct {
150 Name string `json:"name"`
151}
152
gio6ec78542024-06-12 11:21:18 +0400153func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400154 var req createUserReq
gio6ec78542024-06-12 11:21:18 +0400155 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
156 http.Error(w, err.Error(), http.StatusBadRequest)
157 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400158 }
gio3cabc3e2024-10-06 18:37:27 +0400159 if err := s.client.createUser(req.Name); err != nil && !errors.Is(err, ErrorAlreadyExists) {
gio6ec78542024-06-12 11:21:18 +0400160 http.Error(w, err.Error(), http.StatusInternalServerError)
161 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400162 }
163}
164
gio6ec78542024-06-12 11:21:18 +0400165func (s *server) createReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
166 user, ok := mux.Vars(r)["user"]
167 if !ok {
168 http.Error(w, "no user", http.StatusBadRequest)
169 return
170 }
171 if key, err := s.client.createPreAuthKey(user); err != nil {
172 http.Error(w, err.Error(), http.StatusInternalServerError)
173 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400174 } else {
gio6ec78542024-06-12 11:21:18 +0400175 fmt.Fprint(w, key)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400176 }
177}
178
gio864b4332024-09-05 13:56:47 +0400179type expirePreAuthKeyReq struct {
180 AuthKey string `json:"authKey"`
181}
182
183func (s *server) expireReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
184 user, ok := mux.Vars(r)["user"]
185 if !ok {
186 http.Error(w, "no user", http.StatusBadRequest)
187 return
188 }
189 var req expirePreAuthKeyReq
190 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
191 http.Error(w, err.Error(), http.StatusBadRequest)
192 return
193 }
194 if err := s.client.expirePreAuthKey(user, req.AuthKey); err != nil {
gio92116ca2024-10-06 13:55:46 +0400195 if errors.Is(err, ErrorNotFound) {
196 http.Error(w, err.Error(), http.StatusNotFound)
197 } else {
198 http.Error(w, err.Error(), http.StatusInternalServerError)
199 }
gio864b4332024-09-05 13:56:47 +0400200 return
201 }
202}
203
204func (s *server) expireUserNode(w http.ResponseWriter, r *http.Request) {
205 fmt.Println("expire node")
206 user, ok := mux.Vars(r)["user"]
207 if !ok {
208 http.Error(w, "no user", http.StatusBadRequest)
209 return
210 }
211 node, ok := mux.Vars(r)["node"]
212 if !ok {
213 http.Error(w, "no user", http.StatusBadRequest)
214 return
215 }
216 if err := s.client.expireUserNode(user, node); err != nil {
gio92116ca2024-10-06 13:55:46 +0400217 if errors.Is(err, ErrorNotFound) {
218 http.Error(w, err.Error(), http.StatusNotFound)
219 } else {
220 http.Error(w, err.Error(), http.StatusInternalServerError)
221 }
gio864b4332024-09-05 13:56:47 +0400222 return
223 }
224}
225
226func (s *server) removeUserNode(w http.ResponseWriter, r *http.Request) {
227 user, ok := mux.Vars(r)["user"]
228 if !ok {
229 http.Error(w, "no user", http.StatusBadRequest)
230 return
231 }
232 node, ok := mux.Vars(r)["node"]
233 if !ok {
234 http.Error(w, "no user", http.StatusBadRequest)
235 return
236 }
237 if err := s.client.removeUserNode(user, node); err != nil {
gio92116ca2024-10-06 13:55:46 +0400238 if errors.Is(err, ErrorNotFound) {
239 http.Error(w, err.Error(), http.StatusNotFound)
240 } else {
241 http.Error(w, err.Error(), http.StatusInternalServerError)
242 }
gio864b4332024-09-05 13:56:47 +0400243 return
244 }
245}
246
gio43e0aad2025-08-01 16:17:27 +0400247func (s *server) getUserNodes(w http.ResponseWriter, r *http.Request) {
248 user, ok := mux.Vars(r)["user"]
249 if !ok {
250 http.Error(w, "no user", http.StatusBadRequest)
251 return
252 }
253 nodes, err := s.client.getUserNodes(user)
254 if err != nil {
255 if errors.Is(err, ErrorNotFound) {
256 http.Error(w, err.Error(), http.StatusNotFound)
257 } else {
258 http.Error(w, err.Error(), http.StatusInternalServerError)
259 }
260 return
261 }
262 json.NewEncoder(w).Encode(nodes)
263}
264
giob36178f2024-08-23 18:59:15 +0400265func (s *server) handleSyncUsers(_ http.ResponseWriter, _ *http.Request) {
266 go s.syncUsers()
267}
268
269type user struct {
270 Username string `json:"username"`
271}
272
273func (s *server) syncUsers() {
274 resp, err := http.Get(fmt.Sprintf("%s?selfAddress=%s/sync-users", s.fetchUsersAddr, s.self))
275 if err != nil {
276 fmt.Println(err)
277 return
278 }
279 users := []user{}
280 if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
281 fmt.Println(err)
282 return
283 }
284 var usernames []string
285 for _, u := range users {
286 usernames = append(usernames, u.Username)
287 if err := s.client.createUser(u.Username); err != nil && !errors.Is(err, ErrorAlreadyExists) {
288 fmt.Println(err)
289 continue
290 }
291 }
292 currentACLs, err := ioutil.ReadFile(s.aclsPath)
293 if err != nil {
294 fmt.Println(err)
295 }
296 newACLs, err := updateACLs(s.aclsPath, s.cidrs, usernames)
297 if err != nil {
298 fmt.Println(err)
299 panic(err)
300 }
301 if !bytes.Equal(currentACLs, newACLs) {
302 if err := os.Remove(s.aclsReloadPath); err != nil {
303 fmt.Println(err)
304 }
305 }
306}
307
gio6ec78542024-06-12 11:21:18 +0400308func (s *server) enableRoute(w http.ResponseWriter, r *http.Request) {
309 id, ok := mux.Vars(r)["id"]
310 if !ok {
311 http.Error(w, "no id", http.StatusBadRequest)
312 return
313 }
314 if err := s.client.enableRoute(id); err != nil {
315 http.Error(w, err.Error(), http.StatusInternalServerError)
316 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400317 }
318}
319
giof6ad2982024-08-23 17:42:49 +0400320func (s *server) getNodeIP(w http.ResponseWriter, r *http.Request) {
321 user, ok := mux.Vars(r)["user"]
322 if !ok || user == "" {
323 http.Error(w, "no user", http.StatusBadRequest)
324 return
325 }
326 node, ok := mux.Vars(r)["node"]
327 if !ok || node == "" {
328 http.Error(w, "no name", http.StatusBadRequest)
329 return
330 }
331 addr, err := s.client.getNodeAddresses(user, node)
332 if err != nil {
333 if errors.Is(err, ErrorNotFound) {
334 http.Error(w, err.Error(), http.StatusNotFound)
335 } else {
336 http.Error(w, err.Error(), http.StatusInternalServerError)
337 }
338 return
339 }
340 if len(addr) == 0 || addr[0] == nil {
341 http.Error(w, "no address", http.StatusPreconditionFailed)
342 return
343 }
344 fmt.Fprintf(w, "%s", addr[0].String())
345}
346
giob36178f2024-08-23 18:59:15 +0400347func updateACLs(aclsPath string, cidrs []string, users []string) ([]byte, error) {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400348 tmpl, err := template.New("acls").Parse(defaultACLs)
349 if err != nil {
giob36178f2024-08-23 18:59:15 +0400350 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400351 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400352 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400353 if err != nil {
giob36178f2024-08-23 18:59:15 +0400354 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400355 }
356 defer out.Close()
giob36178f2024-08-23 18:59:15 +0400357 var ret bytes.Buffer
358 if err := tmpl.Execute(io.MultiWriter(out, &ret), map[string]any{
gioc23530e2024-05-01 11:06:09 +0400359 "cidrs": cidrs,
giob36178f2024-08-23 18:59:15 +0400360 "users": users,
361 }); err != nil {
362 return nil, err
363 }
364 return ret.Bytes(), nil
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400365}
366
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400367func main() {
368 flag.Parse()
gioc23530e2024-05-01 11:06:09 +0400369 var cidrs []string
370 for _, ips := range strings.Split(*ipSubnet, ",") {
371 _, cidr, err := net.ParseCIDR(ips)
372 if err != nil {
373 panic(err)
374 }
375 cidrs = append(cidrs, cidr.String())
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400376 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400377 c := newClient(*config)
giob36178f2024-08-23 18:59:15 +0400378 s := newServer(*port, c, *fetchUsersAddr, *self, *acls, cidrs)
gio6ec78542024-06-12 11:21:18 +0400379 log.Fatal(s.start())
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400380}