blob: 698d9d269bb323515f37f1eb3e15527c28b07528 [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": [
gioc23530e2024-05-01 11:06:09 +040042 {{- range .cidrs }}
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040043 { // Everyone has passthough access to private-network-proxy node
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040044 "action": "accept",
45 "src": ["*"],
gioc23530e2024-05-01 11:06:09 +040046 "dst": ["{{ . }}:*", "private-network-proxy:0"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040047 },
gioc23530e2024-05-01 11:06:09 +040048 {{- end }}
giob36178f2024-08-23 18:59:15 +040049 {{- range .users }}
50 { // Everyone has passthough access to private-network-proxy node
51 "action": "accept",
52 "src": ["{{ . }}"],
53 "dst": ["{{ . }}:*"],
54 },
55 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040056 ],
57}
58`
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040059
60type server struct {
giob36178f2024-08-23 18:59:15 +040061 port int
62 client *client
63 fetchUsersAddr string
64 self string
65 aclsPath string
66 aclsReloadPath string
67 cidrs []string
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040068}
69
giob36178f2024-08-23 18:59:15 +040070func newServer(port int, client *client, fetchUsersAddr, self, aclsPath string, cidrs []string) *server {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040071 return &server{
72 port,
73 client,
giob36178f2024-08-23 18:59:15 +040074 fetchUsersAddr,
75 self,
76 aclsPath,
77 fmt.Sprintf("%s-reload", aclsPath), // TODO(gio): take from the flag
78 cidrs,
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040079 }
80}
81
gio6ec78542024-06-12 11:21:18 +040082func (s *server) start() error {
giob36178f2024-08-23 18:59:15 +040083 f, err := os.Create(s.aclsReloadPath)
84 if err != nil {
85 return err
86 }
87 f.Close()
gio6ec78542024-06-12 11:21:18 +040088 r := mux.NewRouter()
giob36178f2024-08-23 18:59:15 +040089 r.HandleFunc("/sync-users", s.handleSyncUsers).Methods(http.MethodGet)
gio6ec78542024-06-12 11:21:18 +040090 r.HandleFunc("/user/{user}/preauthkey", s.createReusablePreAuthKey).Methods(http.MethodPost)
gio864b4332024-09-05 13:56:47 +040091 r.HandleFunc("/user/{user}/preauthkey", s.expireReusablePreAuthKey).Methods(http.MethodDelete)
92 r.HandleFunc("/user/{user}/node/{node}/expire", s.expireUserNode).Methods(http.MethodPost)
93 r.HandleFunc("/user/{user}/node/{node}", s.removeUserNode).Methods(http.MethodDelete)
gio6ec78542024-06-12 11:21:18 +040094 r.HandleFunc("/user", s.createUser).Methods(http.MethodPost)
95 r.HandleFunc("/routes/{id}/enable", s.enableRoute).Methods(http.MethodPost)
giob36178f2024-08-23 18:59:15 +040096 go func() {
97 rand.Seed(uint64(time.Now().UnixNano()))
98 s.syncUsers()
99 for {
100 delay := time.Duration(rand.Intn(60)+60) * time.Second
101 time.Sleep(delay)
102 s.syncUsers()
103 }
104 }()
gio6ec78542024-06-12 11:21:18 +0400105 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400106}
107
108type createUserReq struct {
109 Name string `json:"name"`
110}
111
gio6ec78542024-06-12 11:21:18 +0400112func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400113 var req createUserReq
gio6ec78542024-06-12 11:21:18 +0400114 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
115 http.Error(w, err.Error(), http.StatusBadRequest)
116 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400117 }
118 if err := s.client.createUser(req.Name); err != nil {
gio6ec78542024-06-12 11:21:18 +0400119 http.Error(w, err.Error(), http.StatusInternalServerError)
120 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400121 }
122}
123
gio6ec78542024-06-12 11:21:18 +0400124func (s *server) createReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
125 user, ok := mux.Vars(r)["user"]
126 if !ok {
127 http.Error(w, "no user", http.StatusBadRequest)
128 return
129 }
130 if key, err := s.client.createPreAuthKey(user); err != nil {
131 http.Error(w, err.Error(), http.StatusInternalServerError)
132 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400133 } else {
gio6ec78542024-06-12 11:21:18 +0400134 fmt.Fprint(w, key)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400135 }
136}
137
gio864b4332024-09-05 13:56:47 +0400138type expirePreAuthKeyReq struct {
139 AuthKey string `json:"authKey"`
140}
141
142func (s *server) expireReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
143 user, ok := mux.Vars(r)["user"]
144 if !ok {
145 http.Error(w, "no user", http.StatusBadRequest)
146 return
147 }
148 var req expirePreAuthKeyReq
149 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
150 http.Error(w, err.Error(), http.StatusBadRequest)
151 return
152 }
153 if err := s.client.expirePreAuthKey(user, req.AuthKey); err != nil {
154 http.Error(w, err.Error(), http.StatusInternalServerError)
155 return
156 }
157}
158
159func (s *server) expireUserNode(w http.ResponseWriter, r *http.Request) {
160 fmt.Println("expire node")
161 user, ok := mux.Vars(r)["user"]
162 if !ok {
163 http.Error(w, "no user", http.StatusBadRequest)
164 return
165 }
166 node, ok := mux.Vars(r)["node"]
167 if !ok {
168 http.Error(w, "no user", http.StatusBadRequest)
169 return
170 }
171 if err := s.client.expireUserNode(user, node); err != nil {
172 fmt.Println(err)
173 http.Error(w, err.Error(), http.StatusInternalServerError)
174 return
175 }
176}
177
178func (s *server) removeUserNode(w http.ResponseWriter, r *http.Request) {
179 user, ok := mux.Vars(r)["user"]
180 if !ok {
181 http.Error(w, "no user", http.StatusBadRequest)
182 return
183 }
184 node, ok := mux.Vars(r)["node"]
185 if !ok {
186 http.Error(w, "no user", http.StatusBadRequest)
187 return
188 }
189 if err := s.client.removeUserNode(user, node); err != nil {
190 http.Error(w, err.Error(), http.StatusInternalServerError)
191 return
192 }
193}
194
giob36178f2024-08-23 18:59:15 +0400195func (s *server) handleSyncUsers(_ http.ResponseWriter, _ *http.Request) {
196 go s.syncUsers()
197}
198
199type user struct {
200 Username string `json:"username"`
201}
202
203func (s *server) syncUsers() {
204 resp, err := http.Get(fmt.Sprintf("%s?selfAddress=%s/sync-users", s.fetchUsersAddr, s.self))
205 if err != nil {
206 fmt.Println(err)
207 return
208 }
209 users := []user{}
210 if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
211 fmt.Println(err)
212 return
213 }
214 var usernames []string
215 for _, u := range users {
216 usernames = append(usernames, u.Username)
217 if err := s.client.createUser(u.Username); err != nil && !errors.Is(err, ErrorAlreadyExists) {
218 fmt.Println(err)
219 continue
220 }
221 }
222 currentACLs, err := ioutil.ReadFile(s.aclsPath)
223 if err != nil {
224 fmt.Println(err)
225 }
226 newACLs, err := updateACLs(s.aclsPath, s.cidrs, usernames)
227 if err != nil {
228 fmt.Println(err)
229 panic(err)
230 }
231 if !bytes.Equal(currentACLs, newACLs) {
232 if err := os.Remove(s.aclsReloadPath); err != nil {
233 fmt.Println(err)
234 }
235 }
236}
237
gio6ec78542024-06-12 11:21:18 +0400238func (s *server) enableRoute(w http.ResponseWriter, r *http.Request) {
239 id, ok := mux.Vars(r)["id"]
240 if !ok {
241 http.Error(w, "no id", http.StatusBadRequest)
242 return
243 }
244 if err := s.client.enableRoute(id); err != nil {
245 http.Error(w, err.Error(), http.StatusInternalServerError)
246 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400247 }
248}
249
giob36178f2024-08-23 18:59:15 +0400250func updateACLs(aclsPath string, cidrs []string, users []string) ([]byte, error) {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400251 tmpl, err := template.New("acls").Parse(defaultACLs)
252 if err != nil {
giob36178f2024-08-23 18:59:15 +0400253 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400254 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400255 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400256 if err != nil {
giob36178f2024-08-23 18:59:15 +0400257 return nil, err
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400258 }
259 defer out.Close()
giob36178f2024-08-23 18:59:15 +0400260 var ret bytes.Buffer
261 if err := tmpl.Execute(io.MultiWriter(out, &ret), map[string]any{
gioc23530e2024-05-01 11:06:09 +0400262 "cidrs": cidrs,
giob36178f2024-08-23 18:59:15 +0400263 "users": users,
264 }); err != nil {
265 return nil, err
266 }
267 return ret.Bytes(), nil
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400268}
269
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400270func main() {
271 flag.Parse()
gioc23530e2024-05-01 11:06:09 +0400272 var cidrs []string
273 for _, ips := range strings.Split(*ipSubnet, ",") {
274 _, cidr, err := net.ParseCIDR(ips)
275 if err != nil {
276 panic(err)
277 }
278 cidrs = append(cidrs, cidr.String())
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400279 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400280 c := newClient(*config)
giob36178f2024-08-23 18:59:15 +0400281 s := newServer(*port, c, *fetchUsersAddr, *self, *acls, cidrs)
gio6ec78542024-06-12 11:21:18 +0400282 log.Fatal(s.start())
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400283}