blob: 87d245b650f5b3afc22f9c961d02fecc26847ab1 [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"
11 "text/template"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040012
13 "github.com/labstack/echo/v4"
14)
15
16var port = flag.Int("port", 3000, "Port to listen on")
17var config = flag.String("config", "", "Path to headscale config")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040018var acls = flag.String("acls", "", "Path to the headscale acls file")
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040019var ipSubnet = flag.String("ip-subnet", "10.1.0.0/24", "IP subnet of the private network")
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040020
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040021// TODO(gio): make internal network cidr and proxy user configurable
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040022const defaultACLs = `
23{
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040024 "autoApprovers": {
25 "routes": {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040026 "{{ .ipSubnet }}": ["*"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040027 },
28 },
29 "acls": [
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +040030 { // Everyone has passthough access to private-network-proxy node
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040031 "action": "accept",
32 "src": ["*"],
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040033 "dst": ["{{ .ipSubnet }}:*", "private-network-proxy:0"],
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040034 },
35 ],
36}
37`
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040038
39type server struct {
40 port int
41 client *client
42}
43
44func newServer(port int, client *client) *server {
45 return &server{
46 port,
47 client,
48 }
49}
50
51func (s *server) start() {
52 e := echo.New()
53 e.POST("/user/:user/preauthkey", s.createReusablePreAuthKey)
54 e.POST("/user", s.createUser)
55 e.POST("/routes/:id/enable", s.enableRoute)
56 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
57}
58
59type createUserReq struct {
60 Name string `json:"name"`
61}
62
63func (s *server) createUser(c echo.Context) error {
64 var req createUserReq
65 if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
66 return err
67 }
68 if err := s.client.createUser(req.Name); err != nil {
69 return err
70 } else {
71 return c.String(http.StatusOK, "")
72 }
73}
74
75func (s *server) createReusablePreAuthKey(c echo.Context) error {
76 if key, err := s.client.createPreAuthKey(c.Param("user")); err != nil {
77 return err
78 } else {
79 return c.String(http.StatusOK, key)
80 }
81}
82
83func (s *server) enableRoute(c echo.Context) error {
84 if err := s.client.enableRoute(c.Param("id")); err != nil {
85 return err
86 } else {
87 return c.String(http.StatusOK, "")
88 }
89}
90
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040091func updateACLs(cidr net.IPNet, aclsPath string) error {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040092 tmpl, err := template.New("acls").Parse(defaultACLs)
93 if err != nil {
94 return err
95 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040096 out, err := os.Create(aclsPath)
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040097 if err != nil {
98 return err
99 }
100 defer out.Close()
101 return tmpl.Execute(out, map[string]any{
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400102 "ipSubnet": cidr.String(),
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400103 })
104}
105
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400106func main() {
107 flag.Parse()
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400108 _, cidr, err := net.ParseCIDR(*ipSubnet)
109 if err != nil {
110 panic(err)
111 }
112 updateACLs(*cidr, *acls)
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +0400113 c := newClient(*config)
114 s := newServer(*port, c)
115 s.start()
116}