blob: de0c2ff924e9b029f670904fcb63ca67416dba3d [file] [log] [blame]
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04001package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
7 "log"
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +04008 "net"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04009 "net/http"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040010 "os"
gioc23530e2024-05-01 11:06:09 +040011 "strings"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040012 "text/template"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040013
gio6ec78542024-06-12 11:21:18 +040014 "github.com/gorilla/mux"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040015)
16
17var port = flag.Int("port", 3000, "Port to listen on")
18var config = flag.String("config", "", "Path to headscale config")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040019var acls = flag.String("acls", "", "Path to the headscale acls file")
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040020var ipSubnet = flag.String("ip-subnet", "10.1.0.0/24", "IP subnet of the private network")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040021
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040022// TODO(gio): make internal network cidr and proxy user configurable
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040023const defaultACLs = `
24{
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040025 "autoApprovers": {
26 "routes": {
gioc23530e2024-05-01 11:06:09 +040027 {{- range .cidrs }}
28 "{{ . }}": ["*"],
29 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040030 },
31 },
32 "acls": [
gioc23530e2024-05-01 11:06:09 +040033 {{- range .cidrs }}
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040034 { // Everyone has passthough access to private-network-proxy node
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040035 "action": "accept",
36 "src": ["*"],
gioc23530e2024-05-01 11:06:09 +040037 "dst": ["{{ . }}:*", "private-network-proxy:0"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040038 },
gioc23530e2024-05-01 11:06:09 +040039 {{- end }}
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040040 ],
41}
42`
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040043
44type server struct {
45 port int
46 client *client
47}
48
49func newServer(port int, client *client) *server {
50 return &server{
51 port,
52 client,
53 }
54}
55
gio6ec78542024-06-12 11:21:18 +040056func (s *server) start() error {
57 r := mux.NewRouter()
58 r.HandleFunc("/user/{user}/preauthkey", s.createReusablePreAuthKey).Methods(http.MethodPost)
59 r.HandleFunc("/user", s.createUser).Methods(http.MethodPost)
60 r.HandleFunc("/routes/{id}/enable", s.enableRoute).Methods(http.MethodPost)
61 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040062}
63
64type createUserReq struct {
65 Name string `json:"name"`
66}
67
gio6ec78542024-06-12 11:21:18 +040068func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040069 var req createUserReq
gio6ec78542024-06-12 11:21:18 +040070 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
71 http.Error(w, err.Error(), http.StatusBadRequest)
72 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040073 }
74 if err := s.client.createUser(req.Name); err != nil {
gio6ec78542024-06-12 11:21:18 +040075 http.Error(w, err.Error(), http.StatusInternalServerError)
76 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040077 }
78}
79
gio6ec78542024-06-12 11:21:18 +040080func (s *server) createReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
81 user, ok := mux.Vars(r)["user"]
82 if !ok {
83 http.Error(w, "no user", http.StatusBadRequest)
84 return
85 }
86 if key, err := s.client.createPreAuthKey(user); err != nil {
87 http.Error(w, err.Error(), http.StatusInternalServerError)
88 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040089 } else {
gio6ec78542024-06-12 11:21:18 +040090 fmt.Fprint(w, key)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040091 }
92}
93
gio6ec78542024-06-12 11:21:18 +040094func (s *server) enableRoute(w http.ResponseWriter, r *http.Request) {
95 id, ok := mux.Vars(r)["id"]
96 if !ok {
97 http.Error(w, "no id", http.StatusBadRequest)
98 return
99 }
100 if err := s.client.enableRoute(id); err != nil {
101 http.Error(w, err.Error(), http.StatusInternalServerError)
102 return
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400103 }
104}
105
gioc23530e2024-05-01 11:06:09 +0400106func updateACLs(cidrs []string, aclsPath string) error {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400107 tmpl, err := template.New("acls").Parse(defaultACLs)
108 if err != nil {
109 return err
110 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400111 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400112 if err != nil {
113 return err
114 }
115 defer out.Close()
gioc23530e2024-05-01 11:06:09 +0400116 tmpl.Execute(os.Stdout, map[string]any{
117 "cidrs": cidrs,
118 })
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400119 return tmpl.Execute(out, map[string]any{
gioc23530e2024-05-01 11:06:09 +0400120 "cidrs": cidrs,
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400121 })
122}
123
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400124func main() {
125 flag.Parse()
gioc23530e2024-05-01 11:06:09 +0400126 var cidrs []string
127 for _, ips := range strings.Split(*ipSubnet, ",") {
128 _, cidr, err := net.ParseCIDR(ips)
129 if err != nil {
130 panic(err)
131 }
132 cidrs = append(cidrs, cidr.String())
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400133 }
gioc23530e2024-05-01 11:06:09 +0400134 updateACLs(cidrs, *acls)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400135 c := newClient(*config)
136 s := newServer(*port, c)
gio6ec78542024-06-12 11:21:18 +0400137 log.Fatal(s.start())
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400138}