blob: 01763836f1e874fd60df455d6e040b492757c8ed [file] [log] [blame]
gio36b23b32024-08-25 12:20:54 +04001package installer
2
3import (
4 "bytes"
gio864b4332024-09-05 13:56:47 +04005 "encoding/json"
gio36b23b32024-08-25 12:20:54 +04006 "errors"
7 "fmt"
8 "io"
giof6ad2982024-08-23 17:42:49 +04009 "net"
gio36b23b32024-08-25 12:20:54 +040010 "net/http"
gio864b4332024-09-05 13:56:47 +040011 "net/url"
gio36b23b32024-08-25 12:20:54 +040012)
13
gio864b4332024-09-05 13:56:47 +040014type VPNAPIClient interface {
15 GenerateAuthKey(username string) (string, error)
16 ExpireKey(username, key string) error
17 ExpireNode(username, node string) error
18 RemoveNode(username, node string) error
giof6ad2982024-08-23 17:42:49 +040019 GetNodeIP(username, node string) (net.IP, error)
gio36b23b32024-08-25 12:20:54 +040020}
21
22type headscaleAPIClient struct {
gio864b4332024-09-05 13:56:47 +040023 c *http.Client
gio36b23b32024-08-25 12:20:54 +040024 apiAddr string
25}
26
gio864b4332024-09-05 13:56:47 +040027func NewHeadscaleAPIClient(apiAddr string) VPNAPIClient {
28 return &headscaleAPIClient{
29 &http.Client{},
30 apiAddr,
31 }
gio36b23b32024-08-25 12:20:54 +040032}
33
gio864b4332024-09-05 13:56:47 +040034func (g *headscaleAPIClient) GenerateAuthKey(username string) (string, error) {
gio36b23b32024-08-25 12:20:54 +040035 resp, err := http.Post(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username), "application/json", nil)
36 if err != nil {
37 return "", err
38 }
39 var buf bytes.Buffer
40 io.Copy(&buf, resp.Body)
41 if resp.StatusCode != http.StatusOK {
42 return "", errors.New(buf.String())
43 }
44 return buf.String(), nil
45}
gio864b4332024-09-05 13:56:47 +040046
47type expirePreAuthKeyReq struct {
48 AuthKey string `json:"authKey"`
49}
50
51func (g *headscaleAPIClient) ExpireKey(username, key string) error {
52 addr, err := url.Parse(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username))
53 if err != nil {
54 return err
55 }
56 var buf bytes.Buffer
57 if err := json.NewEncoder(&buf).Encode(expirePreAuthKeyReq{key}); err != nil {
58 return err
59 }
60 resp, err := g.c.Do(&http.Request{
61 URL: addr,
62 Method: http.MethodDelete,
63 Body: io.NopCloser(&buf),
64 })
65 if err != nil {
66 return err
67 }
gio92116ca2024-10-06 13:55:46 +040068 if resp.StatusCode == http.StatusOK {
69 return nil
70 }
71 if resp.StatusCode == http.StatusNotFound {
72 return ErrorNotFound
gio864b4332024-09-05 13:56:47 +040073 }
74 return nil
75}
76
77func (g *headscaleAPIClient) ExpireNode(username, node string) error {
78 resp, err := g.c.Post(
79 fmt.Sprintf("%s/user/%s/node/%s/expire", g.apiAddr, username, node),
80 "text/plain",
81 nil,
82 )
83 if err != nil {
84 return err
85 }
gio92116ca2024-10-06 13:55:46 +040086 if resp.StatusCode == http.StatusOK {
87 return nil
88 }
89 if resp.StatusCode == http.StatusNotFound {
90 return ErrorNotFound
gio864b4332024-09-05 13:56:47 +040091 }
92 return nil
93}
94
95func (g *headscaleAPIClient) RemoveNode(username, node string) error {
96 addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s", g.apiAddr, username, node))
97 if err != nil {
98 return err
99 }
100 resp, err := g.c.Do(&http.Request{
101 URL: addr,
102 Method: http.MethodDelete,
103 Body: nil,
104 })
105 if err != nil {
106 return err
107 }
gio92116ca2024-10-06 13:55:46 +0400108 if resp.StatusCode == http.StatusOK {
109 return nil
110 }
111 if resp.StatusCode == http.StatusNotFound {
112 return ErrorNotFound
gio864b4332024-09-05 13:56:47 +0400113 }
114 return nil
115}
giof6ad2982024-08-23 17:42:49 +0400116
117func (g *headscaleAPIClient) GetNodeIP(username, node string) (net.IP, error) {
118 addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s/ip", g.apiAddr, username, node))
119 if err != nil {
120 return nil, err
121 }
122 resp, err := g.c.Do(&http.Request{
123 URL: addr,
124 Method: http.MethodGet,
125 Body: nil,
126 })
127 if err != nil {
128 return nil, err
129 }
130 var buf bytes.Buffer
131 if _, err := io.Copy(&buf, resp.Body); err != nil {
132 return nil, err
133 }
134 bufS := buf.String()
135 if resp.StatusCode == http.StatusNotFound {
136 return nil, ErrorNotFound
137 }
138 if resp.StatusCode != http.StatusOK {
139 return nil, errors.New(bufS)
140 }
141 ip := net.ParseIP(bufS)
142 if ip == nil {
143 return nil, fmt.Errorf("invalid ip")
144 }
145 return ip, nil
146}