blob: 194da06aa299c5863b3d050551884849395eadfc [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": [
giof6ad2982024-08-23 17:42:49 +040042 // {
43 // "action": "accept",
44 // "src": ["10.42.0.0/16", "10.43.0.0/16", "135.181.48.180/32", "65.108.39.172/32"],
45 // "dst": ["10.42.0.0/16:*", "10.43.0.0/16:*", "135.181.48.180/32:*", "65.108.39.172/32:*"],
46 // },
gioc23530e2024-05-01 11:06:09 +040047 {{- range .cidrs }}
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040048 { // Everyone has passthough access to private-network-proxy node
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040049 "action": "accept",
50 "src": ["*"],
gioc23530e2024-05-01 11:06:09 +040051 "dst": ["{{ . }}:*", "private-network-proxy:0"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040052 },
gioc23530e2024-05-01 11:06:09 +040053 {{- end }}
giof6ad2982024-08-23 17:42:49 +040054 { // Everyone has access to every port of nodes owned by private-network-proxy
55 "action": "accept",
56 "src": ["*"],
57 "dst": ["private-network-proxy:*"],
58 },
59 {
60 "action": "accept",
61 "src": ["private-network-proxy"],
62 "dst": ["private-network-proxy:*"],
63 },
giob36178f2024-08-23 18:59:15 +040064 {{- range .users }}
giof6ad2982024-08-23 17:42:49 +040065 {
giob36178f2024-08-23 18:59:15 +040066 "action": "accept",
67 "src": ["{{ . }}"],
68 "dst": ["{{ . }}:*"],
69 },
70 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040071 ],
72}
73`
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040074
75type server struct {
giob36178f2024-08-23 18:59:15 +040076 port int
77 client *client
78 fetchUsersAddr string
79 self string
80 aclsPath string
81 aclsReloadPath string
82 cidrs []string
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040083}
84
giob36178f2024-08-23 18:59:15 +040085func newServer(port int, client *client, fetchUsersAddr, self, aclsPath string, cidrs []string) *server {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040086 return &server{
87 port,
88 client,
giob36178f2024-08-23 18:59:15 +040089 fetchUsersAddr,
90 self,
91 aclsPath,
92 fmt.Sprintf("%s-reload", aclsPath), // TODO(gio): take from the flag
93 cidrs,
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040094 }
95}
96
gio6ec78542024-06-12 11:21:18 +040097func (s *server) start() error {
giob36178f2024-08-23 18:59:15 +040098 f, err := os.Create(s.aclsReloadPath)
99 if err != nil {
100 return err
101 }
102 f.Close()
gio6ec78542024-06-12 11:21:18 +0400103 r := mux.NewRouter()
giob36178f2024-08-23 18:59:15 +0400104 r.HandleFunc("/sync-users", s.handleSyncUsers).Methods(http.MethodGet)
gio6ec78542024-06-12 11:21:18 +0400105 r.HandleFunc("/user/{user}/preauthkey", s.createReusablePreAuthKey).Methods(http.MethodPost)
gio864b4332024-09-05 13:56:47 +0400106 r.HandleFunc("/user/{user}/preauthkey", s.expireReusablePreAuthKey).Methods(http.MethodDelete)
107 r.HandleFunc("/user/{user}/node/{node}/expire", s.expireUserNode).Methods(http.MethodPost)
giof6ad2982024-08-23 17:42:49 +0400108 r.HandleFunc("/user/{user}/node/{node}/ip", s.getNodeIP).Methods(http.MethodGet)
gio864b4332024-09-05 13:56:47 +0400109 r.HandleFunc("/user/{user}/node/{node}", s.removeUserNode).Methods(http.MethodDelete)
gio6ec78542024-06-12 11:21:18 +0400110 r.HandleFunc("/user", s.createUser).Methods(http.MethodPost)
111 r.HandleFunc("/routes/{id}/enable", s.enableRoute).Methods(http.MethodPost)
giob36178f2024-08-23 18:59:15 +0400112 go func() {
113 rand.Seed(uint64(time.Now().UnixNano()))
114 s.syncUsers()
115 for {
116 delay := time.Duration(rand.Intn(60)+60) * time.Second
117 time.Sleep(delay)
118 s.syncUsers()
119 }
120 }()
gio6ec78542024-06-12 11:21:18 +0400121 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400122}
123
124type createUserReq struct {
125 Name string `json:"name"`
126}
127
gio6ec78542024-06-12 11:21:18 +0400128func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400129 var req createUserReq
gio6ec78542024-06-12 11:21:18 +0400130 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
131 http.Error(w, err.Error(), http.StatusBadRequest)
132 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400133 }
134 if err := s.client.createUser(req.Name); err != nil {
gio6ec78542024-06-12 11:21:18 +0400135 http.Error(w, err.Error(), http.StatusInternalServerError)
136 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400137 }
138}
139
gio6ec78542024-06-12 11:21:18 +0400140func (s *server) createReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
141 user, ok := mux.Vars(r)["user"]
142 if !ok {
143 http.Error(w, "no user", http.StatusBadRequest)
144 return
145 }
146 if key, err := s.client.createPreAuthKey(user); err != nil {
147 http.Error(w, err.Error(), http.StatusInternalServerError)
148 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400149 } else {
gio6ec78542024-06-12 11:21:18 +0400150 fmt.Fprint(w, key)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400151 }
152}
153
gio864b4332024-09-05 13:56:47 +0400154type expirePreAuthKeyReq struct {
155 AuthKey string `json:"authKey"`
156}
157
158func (s *server) expireReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
159 user, ok := mux.Vars(r)["user"]
160 if !ok {
161 http.Error(w, "no user", http.StatusBadRequest)
162 return
163 }
164 var req expirePreAuthKeyReq
165 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
166 http.Error(w, err.Error(), http.StatusBadRequest)
167 return
168 }
169 if err := s.client.expirePreAuthKey(user, req.AuthKey); err != nil {
170 http.Error(w, err.Error(), http.StatusInternalServerError)
171 return
172 }
173}
174
175func (s *server) expireUserNode(w http.ResponseWriter, r *http.Request) {
176 fmt.Println("expire node")
177 user, ok := mux.Vars(r)["user"]
178 if !ok {
179 http.Error(w, "no user", http.StatusBadRequest)
180 return
181 }
182 node, ok := mux.Vars(r)["node"]
183 if !ok {
184 http.Error(w, "no user", http.StatusBadRequest)
185 return
186 }
187 if err := s.client.expireUserNode(user, node); err != nil {
188 fmt.Println(err)
189 http.Error(w, err.Error(), http.StatusInternalServerError)
190 return
191 }
192}
193
194func (s *server) removeUserNode(w http.ResponseWriter, r *http.Request) {
195 user, ok := mux.Vars(r)["user"]
196 if !ok {
197 http.Error(w, "no user", http.StatusBadRequest)
198 return
199 }
200 node, ok := mux.Vars(r)["node"]
201 if !ok {
202 http.Error(w, "no user", http.StatusBadRequest)
203 return
204 }
205 if err := s.client.removeUserNode(user, node); err != nil {
206 http.Error(w, err.Error(), http.StatusInternalServerError)
207 return
208 }
209}
210
giob36178f2024-08-23 18:59:15 +0400211func (s *server) handleSyncUsers(_ http.ResponseWriter, _ *http.Request) {
212 go s.syncUsers()
213}
214
215type user struct {
216 Username string `json:"username"`
217}
218
219func (s *server) syncUsers() {
220 resp, err := http.Get(fmt.Sprintf("%s?selfAddress=%s/sync-users", s.fetchUsersAddr, s.self))
221 if err != nil {
222 fmt.Println(err)
223 return
224 }
225 users := []user{}
226 if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
227 fmt.Println(err)
228 return
229 }
230 var usernames []string
231 for _, u := range users {
232 usernames = append(usernames, u.Username)
233 if err := s.client.createUser(u.Username); err != nil && !errors.Is(err, ErrorAlreadyExists) {
234 fmt.Println(err)
235 continue
236 }
237 }
238 currentACLs, err := ioutil.ReadFile(s.aclsPath)
239 if err != nil {
240 fmt.Println(err)
241 }
242 newACLs, err := updateACLs(s.aclsPath, s.cidrs, usernames)
243 if err != nil {
244 fmt.Println(err)
245 panic(err)
246 }
247 if !bytes.Equal(currentACLs, newACLs) {
248 if err := os.Remove(s.aclsReloadPath); err != nil {
249 fmt.Println(err)
250 }
251 }
252}
253
gio6ec78542024-06-12 11:21:18 +0400254func (s *server) enableRoute(w http.ResponseWriter, r *http.Request) {
255 id, ok := mux.Vars(r)["id"]
256 if !ok {
257 http.Error(w, "no id", http.StatusBadRequest)
258 return
259 }
260 if err := s.client.enableRoute(id); err != nil {
261 http.Error(w, err.Error(), http.StatusInternalServerError)
262 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400263 }
264}
265
giof6ad2982024-08-23 17:42:49 +0400266func (s *server) getNodeIP(w http.ResponseWriter, r *http.Request) {
267 user, ok := mux.Vars(r)["user"]
268 if !ok || user == "" {
269 http.Error(w, "no user", http.StatusBadRequest)
270 return
271 }
272 node, ok := mux.Vars(r)["node"]
273 if !ok || node == "" {
274 http.Error(w, "no name", http.StatusBadRequest)
275 return
276 }
277 addr, err := s.client.getNodeAddresses(user, node)
278 if err != nil {
279 if errors.Is(err, ErrorNotFound) {
280 http.Error(w, err.Error(), http.StatusNotFound)
281 } else {
282 http.Error(w, err.Error(), http.StatusInternalServerError)
283 }
284 return
285 }
286 if len(addr) == 0 || addr[0] == nil {
287 http.Error(w, "no address", http.StatusPreconditionFailed)
288 return
289 }
290 fmt.Fprintf(w, "%s", addr[0].String())
291}
292
giob36178f2024-08-23 18:59:15 +0400293func updateACLs(aclsPath string, cidrs []string, users []string) ([]byte, error) {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400294 tmpl, err := template.New("acls").Parse(defaultACLs)
295 if err != nil {
giob36178f2024-08-23 18:59:15 +0400296 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400297 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400298 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400299 if err != nil {
giob36178f2024-08-23 18:59:15 +0400300 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400301 }
302 defer out.Close()
giob36178f2024-08-23 18:59:15 +0400303 var ret bytes.Buffer
304 if err := tmpl.Execute(io.MultiWriter(out, &ret), map[string]any{
gioc23530e2024-05-01 11:06:09 +0400305 "cidrs": cidrs,
giob36178f2024-08-23 18:59:15 +0400306 "users": users,
307 }); err != nil {
308 return nil, err
309 }
310 return ret.Bytes(), nil
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400311}
312
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400313func main() {
314 flag.Parse()
gioc23530e2024-05-01 11:06:09 +0400315 var cidrs []string
316 for _, ips := range strings.Split(*ipSubnet, ",") {
317 _, cidr, err := net.ParseCIDR(ips)
318 if err != nil {
319 panic(err)
320 }
321 cidrs = append(cidrs, cidr.String())
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400322 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400323 c := newClient(*config)
giob36178f2024-08-23 18:59:15 +0400324 s := newServer(*port, c, *fetchUsersAddr, *self, *acls, cidrs)
gio6ec78542024-06-12 11:21:18 +0400325 log.Fatal(s.start())
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400326}