blob: fb20274005412e8578190fecf460f5598c0e3224 [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)
giob36178f2024-08-23 18:59:15 +0400118 go func() {
119 rand.Seed(uint64(time.Now().UnixNano()))
120 s.syncUsers()
121 for {
122 delay := time.Duration(rand.Intn(60)+60) * time.Second
123 time.Sleep(delay)
124 s.syncUsers()
125 }
126 }()
gio6ec78542024-06-12 11:21:18 +0400127 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400128}
129
130type createUserReq struct {
131 Name string `json:"name"`
132}
133
gio6ec78542024-06-12 11:21:18 +0400134func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400135 var req createUserReq
gio6ec78542024-06-12 11:21:18 +0400136 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
137 http.Error(w, err.Error(), http.StatusBadRequest)
138 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400139 }
gio3cabc3e2024-10-06 18:37:27 +0400140 if err := s.client.createUser(req.Name); err != nil && !errors.Is(err, ErrorAlreadyExists) {
gio6ec78542024-06-12 11:21:18 +0400141 http.Error(w, err.Error(), http.StatusInternalServerError)
142 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400143 }
144}
145
gio6ec78542024-06-12 11:21:18 +0400146func (s *server) createReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
147 user, ok := mux.Vars(r)["user"]
148 if !ok {
149 http.Error(w, "no user", http.StatusBadRequest)
150 return
151 }
152 if key, err := s.client.createPreAuthKey(user); err != nil {
153 http.Error(w, err.Error(), http.StatusInternalServerError)
154 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400155 } else {
gio6ec78542024-06-12 11:21:18 +0400156 fmt.Fprint(w, key)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400157 }
158}
159
gio864b4332024-09-05 13:56:47 +0400160type expirePreAuthKeyReq struct {
161 AuthKey string `json:"authKey"`
162}
163
164func (s *server) expireReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
165 user, ok := mux.Vars(r)["user"]
166 if !ok {
167 http.Error(w, "no user", http.StatusBadRequest)
168 return
169 }
170 var req expirePreAuthKeyReq
171 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
172 http.Error(w, err.Error(), http.StatusBadRequest)
173 return
174 }
175 if err := s.client.expirePreAuthKey(user, req.AuthKey); err != nil {
gio92116ca2024-10-06 13:55:46 +0400176 if errors.Is(err, ErrorNotFound) {
177 http.Error(w, err.Error(), http.StatusNotFound)
178 } else {
179 http.Error(w, err.Error(), http.StatusInternalServerError)
180 }
gio864b4332024-09-05 13:56:47 +0400181 return
182 }
183}
184
185func (s *server) expireUserNode(w http.ResponseWriter, r *http.Request) {
186 fmt.Println("expire node")
187 user, ok := mux.Vars(r)["user"]
188 if !ok {
189 http.Error(w, "no user", http.StatusBadRequest)
190 return
191 }
192 node, ok := mux.Vars(r)["node"]
193 if !ok {
194 http.Error(w, "no user", http.StatusBadRequest)
195 return
196 }
197 if err := s.client.expireUserNode(user, node); err != nil {
gio92116ca2024-10-06 13:55:46 +0400198 if errors.Is(err, ErrorNotFound) {
199 http.Error(w, err.Error(), http.StatusNotFound)
200 } else {
201 http.Error(w, err.Error(), http.StatusInternalServerError)
202 }
gio864b4332024-09-05 13:56:47 +0400203 return
204 }
205}
206
207func (s *server) removeUserNode(w http.ResponseWriter, r *http.Request) {
208 user, ok := mux.Vars(r)["user"]
209 if !ok {
210 http.Error(w, "no user", http.StatusBadRequest)
211 return
212 }
213 node, ok := mux.Vars(r)["node"]
214 if !ok {
215 http.Error(w, "no user", http.StatusBadRequest)
216 return
217 }
218 if err := s.client.removeUserNode(user, node); err != nil {
gio92116ca2024-10-06 13:55:46 +0400219 if errors.Is(err, ErrorNotFound) {
220 http.Error(w, err.Error(), http.StatusNotFound)
221 } else {
222 http.Error(w, err.Error(), http.StatusInternalServerError)
223 }
gio864b4332024-09-05 13:56:47 +0400224 return
225 }
226}
227
gio43e0aad2025-08-01 16:17:27 +0400228func (s *server) getUserNodes(w http.ResponseWriter, r *http.Request) {
229 user, ok := mux.Vars(r)["user"]
230 if !ok {
231 http.Error(w, "no user", http.StatusBadRequest)
232 return
233 }
234 nodes, err := s.client.getUserNodes(user)
235 if err != nil {
236 if errors.Is(err, ErrorNotFound) {
237 http.Error(w, err.Error(), http.StatusNotFound)
238 } else {
239 http.Error(w, err.Error(), http.StatusInternalServerError)
240 }
241 return
242 }
243 json.NewEncoder(w).Encode(nodes)
244}
245
giob36178f2024-08-23 18:59:15 +0400246func (s *server) handleSyncUsers(_ http.ResponseWriter, _ *http.Request) {
247 go s.syncUsers()
248}
249
250type user struct {
251 Username string `json:"username"`
252}
253
254func (s *server) syncUsers() {
255 resp, err := http.Get(fmt.Sprintf("%s?selfAddress=%s/sync-users", s.fetchUsersAddr, s.self))
256 if err != nil {
257 fmt.Println(err)
258 return
259 }
260 users := []user{}
261 if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
262 fmt.Println(err)
263 return
264 }
265 var usernames []string
266 for _, u := range users {
267 usernames = append(usernames, u.Username)
268 if err := s.client.createUser(u.Username); err != nil && !errors.Is(err, ErrorAlreadyExists) {
269 fmt.Println(err)
270 continue
271 }
272 }
273 currentACLs, err := ioutil.ReadFile(s.aclsPath)
274 if err != nil {
275 fmt.Println(err)
276 }
277 newACLs, err := updateACLs(s.aclsPath, s.cidrs, usernames)
278 if err != nil {
279 fmt.Println(err)
280 panic(err)
281 }
282 if !bytes.Equal(currentACLs, newACLs) {
283 if err := os.Remove(s.aclsReloadPath); err != nil {
284 fmt.Println(err)
285 }
286 }
287}
288
gio6ec78542024-06-12 11:21:18 +0400289func (s *server) enableRoute(w http.ResponseWriter, r *http.Request) {
290 id, ok := mux.Vars(r)["id"]
291 if !ok {
292 http.Error(w, "no id", http.StatusBadRequest)
293 return
294 }
295 if err := s.client.enableRoute(id); err != nil {
296 http.Error(w, err.Error(), http.StatusInternalServerError)
297 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400298 }
299}
300
giof6ad2982024-08-23 17:42:49 +0400301func (s *server) getNodeIP(w http.ResponseWriter, r *http.Request) {
302 user, ok := mux.Vars(r)["user"]
303 if !ok || user == "" {
304 http.Error(w, "no user", http.StatusBadRequest)
305 return
306 }
307 node, ok := mux.Vars(r)["node"]
308 if !ok || node == "" {
309 http.Error(w, "no name", http.StatusBadRequest)
310 return
311 }
312 addr, err := s.client.getNodeAddresses(user, node)
313 if err != nil {
314 if errors.Is(err, ErrorNotFound) {
315 http.Error(w, err.Error(), http.StatusNotFound)
316 } else {
317 http.Error(w, err.Error(), http.StatusInternalServerError)
318 }
319 return
320 }
321 if len(addr) == 0 || addr[0] == nil {
322 http.Error(w, "no address", http.StatusPreconditionFailed)
323 return
324 }
325 fmt.Fprintf(w, "%s", addr[0].String())
326}
327
giob36178f2024-08-23 18:59:15 +0400328func updateACLs(aclsPath string, cidrs []string, users []string) ([]byte, error) {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400329 tmpl, err := template.New("acls").Parse(defaultACLs)
330 if err != nil {
giob36178f2024-08-23 18:59:15 +0400331 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400332 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400333 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400334 if err != nil {
giob36178f2024-08-23 18:59:15 +0400335 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400336 }
337 defer out.Close()
giob36178f2024-08-23 18:59:15 +0400338 var ret bytes.Buffer
339 if err := tmpl.Execute(io.MultiWriter(out, &ret), map[string]any{
gioc23530e2024-05-01 11:06:09 +0400340 "cidrs": cidrs,
giob36178f2024-08-23 18:59:15 +0400341 "users": users,
342 }); err != nil {
343 return nil, err
344 }
345 return ret.Bytes(), nil
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400346}
347
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400348func main() {
349 flag.Parse()
gioc23530e2024-05-01 11:06:09 +0400350 var cidrs []string
351 for _, ips := range strings.Split(*ipSubnet, ",") {
352 _, cidr, err := net.ParseCIDR(ips)
353 if err != nil {
354 panic(err)
355 }
356 cidrs = append(cidrs, cidr.String())
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400357 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400358 c := newClient(*config)
giob36178f2024-08-23 18:59:15 +0400359 s := newServer(*port, c, *fetchUsersAddr, *self, *acls, cidrs)
gio6ec78542024-06-12 11:21:18 +0400360 log.Fatal(s.start())
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400361}