blob: 6bc89e410e6058764ce74ecb2269d255e0710786 [file] [log] [blame]
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +04001package main
2
3import (
giob1c4e542024-07-15 12:10:52 +04004 "encoding/base64"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +04005 "encoding/json"
gioc76baed2024-08-19 22:04:57 +04006 "errors"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +04007 "flag"
8 "fmt"
9 "io"
gioc76baed2024-08-19 22:04:57 +040010 "io/fs"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040011 "log"
gioc76baed2024-08-19 22:04:57 +040012 "math/rand"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040013 "net/http"
14 "os"
15 "strconv"
16 "strings"
gioefa0ed42024-06-13 12:31:43 +040017 "sync"
18 "time"
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040019
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040020 "github.com/giolekva/pcloud/core/installer/soft"
21
22 "golang.org/x/crypto/ssh"
23)
24
Davit Tabidze6bf29832024-06-17 16:51:54 +040025const (
26 secretLength = 20
gioc76baed2024-08-19 22:04:57 +040027 start = 49152
28 end = 65535
Davit Tabidze6bf29832024-06-17 16:51:54 +040029)
30
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040031var port = flag.Int("port", 8080, "Port to listen on")
32var repoAddr = flag.String("repo-addr", "", "Git repository address where Helm releases are stored")
33var sshKey = flag.String("ssh-key", "", "Path to SHH key used to connect with Git repository")
34var ingressNginxPath = flag.String("ingress-nginx-path", "", "Path to the ingress-nginx Helm release")
giod28f83c2024-08-15 10:53:40 +040035var minPreOpenPorts = flag.Int("min-pre-open-ports", 5, "Minimum number of pre-open ports to keep in reserve")
36var preOpenPortsBatchSize = flag.Int("pre-open-ports-batch-size", 10, "Number of new ports to open at a time")
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040037
38type client interface {
giod28f83c2024-08-15 10:53:40 +040039 ReservePort() (int, string, error)
gioc76baed2024-08-19 22:04:57 +040040 ReleaseReservedPort(port ...int)
giod28f83c2024-08-15 10:53:40 +040041 AddPortForwarding(protocol string, port int, secret, dest string) error
42 RemovePortForwarding(protocol string, port int) error
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040043}
44
45type repoClient struct {
giod28f83c2024-08-15 10:53:40 +040046 l sync.Locker
47 repo soft.RepoIO
48 path string
gioc76baed2024-08-19 22:04:57 +040049 secretGenerator SecretGenerator
giod28f83c2024-08-15 10:53:40 +040050 minPreOpenPorts int
51 preOpenPortsBatchSize int
52 preOpenPorts []int
53 blocklist map[int]struct{}
54 reserve map[int]string
gioc76baed2024-08-19 22:04:57 +040055 availablePorts []int
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040056}
57
giod28f83c2024-08-15 10:53:40 +040058func newRepoClient(
59 repo soft.RepoIO,
60 path string,
61 minPreOpenPorts int,
62 preOpenPortsBatchSize int,
gioc76baed2024-08-19 22:04:57 +040063 secretGenerator SecretGenerator,
giod28f83c2024-08-15 10:53:40 +040064) (client, error) {
65 ret := &repoClient{
66 l: &sync.Mutex{},
67 repo: repo,
68 path: path,
gioc76baed2024-08-19 22:04:57 +040069 secretGenerator: secretGenerator,
giod28f83c2024-08-15 10:53:40 +040070 minPreOpenPorts: minPreOpenPorts,
71 preOpenPortsBatchSize: preOpenPortsBatchSize,
gioc76baed2024-08-19 22:04:57 +040072 preOpenPorts: []int{},
73 blocklist: map[int]struct{}{},
74 reserve: map[int]string{},
75 availablePorts: []int{},
giod28f83c2024-08-15 10:53:40 +040076 }
gioc76baed2024-08-19 22:04:57 +040077 st, err := ret.readState(repo)
giod28f83c2024-08-15 10:53:40 +040078 if err != nil {
gioc76baed2024-08-19 22:04:57 +040079 if !errors.Is(err, fs.ErrNotExist) {
giod28f83c2024-08-15 10:53:40 +040080 return nil, err
81 }
gioc76baed2024-08-19 22:04:57 +040082 } else {
83 ret.preOpenPorts = st.PreOpenPorts
84 ret.blocklist = st.Blocklist
85 ret.reserve = st.Reserve
giod28f83c2024-08-15 10:53:40 +040086 }
gioc76baed2024-08-19 22:04:57 +040087 for i := start; i < end; i++ {
88 if _, ok := ret.blocklist[i]; !ok {
89 ret.availablePorts = append(ret.availablePorts, i)
90 }
91 }
92 if err := ret.preOpenNewPorts(); err != nil {
93 return nil, err
94 }
95 var reservedPorts []int
96 for k := range ret.reserve {
97 reservedPorts = append(reservedPorts, k)
98 }
99 go func() {
100 time.Sleep(30 * time.Minute)
101 ret.ReleaseReservedPort(reservedPorts...)
102 }()
giod28f83c2024-08-15 10:53:40 +0400103 return ret, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400104}
105
giod28f83c2024-08-15 10:53:40 +0400106func (c *repoClient) ReservePort() (int, string, error) {
107 c.l.Lock()
108 defer c.l.Unlock()
109 if len(c.preOpenPorts) == 0 {
110 return -1, "", fmt.Errorf("no pre-open ports are available")
111 }
112 port := c.preOpenPorts[0]
113 c.preOpenPorts = c.preOpenPorts[1:]
gioc76baed2024-08-19 22:04:57 +0400114 secret, err := c.secretGenerator()
giod28f83c2024-08-15 10:53:40 +0400115 if err != nil {
116 return -1, "", err
117 }
118 c.reserve[port] = secret
119 return port, secret, nil
120}
121
gioc76baed2024-08-19 22:04:57 +0400122func (c *repoClient) ReleaseReservedPort(port ...int) {
123 if len(port) == 0 {
124 return
125 }
giod28f83c2024-08-15 10:53:40 +0400126 c.l.Lock()
127 defer c.l.Unlock()
gioc76baed2024-08-19 22:04:57 +0400128 if _, err := c.repo.Do(func(fs soft.RepoFS) (string, error) {
129 for _, p := range port {
130 delete(c.reserve, p)
131 c.preOpenPorts = append(c.preOpenPorts, p)
132 }
133 if err := c.writeState(fs); err != nil {
134 return "", err
135 }
136 return fmt.Sprintf("Released port reservations: %+v", port), nil
137 }); err != nil {
138 panic(err)
139 }
giod28f83c2024-08-15 10:53:40 +0400140}
141
142type state struct {
143 PreOpenPorts []int `json:"preOpenPorts"`
144 Blocklist map[int]struct{} `json:"blocklist"`
gioc76baed2024-08-19 22:04:57 +0400145 Reserve map[int]string `json:"reserve"`
giod28f83c2024-08-15 10:53:40 +0400146}
147
148func (c *repoClient) preOpenNewPorts() error {
149 c.l.Lock()
150 defer c.l.Unlock()
151 if len(c.preOpenPorts) >= c.minPreOpenPorts {
152 return nil
153 }
154 var ports []int
155 for count := c.preOpenPortsBatchSize; count > 0; count-- {
gioc76baed2024-08-19 22:04:57 +0400156 if len(c.availablePorts) == 0 {
giod28f83c2024-08-15 10:53:40 +0400157 return fmt.Errorf("could not open new port")
158 }
gioc76baed2024-08-19 22:04:57 +0400159 r := rand.Intn(len(c.availablePorts))
160 p := c.availablePorts[r]
161 c.availablePorts[r] = c.availablePorts[len(c.availablePorts)-1]
162 c.availablePorts = c.availablePorts[:len(c.availablePorts)-1]
163 ports = append(ports, p)
164 c.preOpenPorts = append(c.preOpenPorts, p)
165 c.blocklist[p] = struct{}{}
giod28f83c2024-08-15 10:53:40 +0400166 }
gioc76baed2024-08-19 22:04:57 +0400167 _, err := c.repo.Do(func(fs soft.RepoFS) (string, error) {
giod28f83c2024-08-15 10:53:40 +0400168 if err := c.writeState(fs); err != nil {
169 return "", err
170 }
171 rel, err := c.readRelease(fs)
172 if err != nil {
173 return "", err
174 }
gioa344a2a2024-08-16 17:13:48 +0400175 svcType, err := extractString(rel, "spec.values.controller.service.type")
giod28f83c2024-08-15 10:53:40 +0400176 if err != nil {
177 return "", err
178 }
gioa344a2a2024-08-16 17:13:48 +0400179 if svcType == "NodePort" {
180 tcp, err := extractPorts(rel, "spec.values.controller.service.nodePorts.tcp")
181 if err != nil {
182 return "", err
183 }
184 udp, err := extractPorts(rel, "spec.values.controller.service.nodePorts.udp")
185 if err != nil {
186 return "", err
187 }
gioc76baed2024-08-19 22:04:57 +0400188 fmt.Printf("%+v\n", tcp)
189 fmt.Printf("%+v\n", udp)
gioa344a2a2024-08-16 17:13:48 +0400190 for _, p := range ports {
191 ps := strconv.Itoa(p)
192 tcp[ps] = p
193 udp[ps] = p
194 }
gioc76baed2024-08-19 22:04:57 +0400195 fmt.Printf("%+v\n", tcp)
196 fmt.Printf("%+v\n", udp)
gioa344a2a2024-08-16 17:13:48 +0400197 if err := c.writeRelease(fs, rel); err != nil {
198 return "", err
199 }
giod28f83c2024-08-15 10:53:40 +0400200 }
gioa344a2a2024-08-16 17:13:48 +0400201 fmt.Printf("Pre opened new ports: %+v\n", ports)
giod28f83c2024-08-15 10:53:40 +0400202 return "preopen new ports", nil
203 })
gioc76baed2024-08-19 22:04:57 +0400204 return err
giod28f83c2024-08-15 10:53:40 +0400205}
206
207func (c *repoClient) AddPortForwarding(protocol string, port int, secret, dest string) error {
gioc76baed2024-08-19 22:04:57 +0400208 protocol = strings.ToLower(protocol)
giod28f83c2024-08-15 10:53:40 +0400209 defer func() {
gioc76baed2024-08-19 22:04:57 +0400210 if err := c.preOpenNewPorts(); err != nil {
211 panic(err)
212 }
giod28f83c2024-08-15 10:53:40 +0400213 }()
214 c.l.Lock()
215 defer c.l.Unlock()
216 if sec, ok := c.reserve[port]; !ok || sec != secret {
217 return fmt.Errorf("wrong secret")
218 }
219 delete(c.reserve, port)
gioc76baed2024-08-19 22:04:57 +0400220 _, err := c.repo.Do(func(fs soft.RepoFS) (string, error) {
giod28f83c2024-08-15 10:53:40 +0400221 if err := c.writeState(fs); err != nil {
222 return "", err
223 }
224 rel, err := c.readRelease(fs)
225 if err != nil {
226 return "", err
227 }
228 portStr := strconv.Itoa(port)
229 switch protocol {
230 case "tcp":
231 tcp, err := extractPorts(rel, "spec.values.tcp")
232 if err != nil {
233 return "", err
234 }
235 tcp[portStr] = dest
236 case "udp":
237 udp, err := extractPorts(rel, "spec.values.udp")
238 if err != nil {
239 return "", err
240 }
241 udp[portStr] = dest
242 default:
243 panic("MUST NOT REACH")
244 }
245 if err := c.writeRelease(fs, rel); err != nil {
246 return "", err
247 }
248 return fmt.Sprintf("ingress: port %s map %d %s", protocol, port, dest), nil
249 })
gioc76baed2024-08-19 22:04:57 +0400250 return err
giod28f83c2024-08-15 10:53:40 +0400251}
252
253func (c *repoClient) RemovePortForwarding(protocol string, port int) error {
gioc76baed2024-08-19 22:04:57 +0400254 protocol = strings.ToLower(protocol)
giod28f83c2024-08-15 10:53:40 +0400255 c.l.Lock()
256 defer c.l.Unlock()
gioc76baed2024-08-19 22:04:57 +0400257 _, err := c.repo.Do(func(fs soft.RepoFS) (string, error) {
giod28f83c2024-08-15 10:53:40 +0400258 rel, err := c.readRelease(fs)
259 if err != nil {
260 return "", err
261 }
262 switch protocol {
263 case "tcp":
264 tcp, err := extractPorts(rel, "spec.values.tcp")
265 if err != nil {
266 return "", err
267 }
268 if err := removePort(tcp, port); err != nil {
269 return "", err
270 }
271 case "udp":
272 udp, err := extractPorts(rel, "spec.values.udp")
273 if err != nil {
274 return "", err
275 }
276 if err := removePort(udp, port); err != nil {
277 return "", err
278 }
279 default:
280 panic("MUST NOT REACH")
281 }
gioa344a2a2024-08-16 17:13:48 +0400282 svcType, err := extractString(rel, "spec.values.controller.service.type")
giod28f83c2024-08-15 10:53:40 +0400283 if err != nil {
284 return "", err
285 }
gioa344a2a2024-08-16 17:13:48 +0400286 if svcType == "NodePort" {
287 svcTCP, err := extractPorts(rel, "spec.values.controller.service.nodePorts.tcp")
288 if err != nil {
289 return "", err
290 }
291 svcUDP, err := extractPorts(rel, "spec.values.controller.service.nodePorts.udp")
292 if err != nil {
293 return "", err
294 }
295 if err := removePort(svcTCP, port); err != nil {
296 return "", err
297 }
298 if err := removePort(svcUDP, port); err != nil {
299 return "", err
300 }
giod28f83c2024-08-15 10:53:40 +0400301 }
302 if err := c.writeRelease(fs, rel); err != nil {
303 return "", err
304 }
305 return fmt.Sprintf("ingress: remove %s port map %d", protocol, port), nil
306 })
gioc76baed2024-08-19 22:04:57 +0400307 return err
308}
309
310func (c *repoClient) readState(fs soft.RepoFS) (state, error) {
311 r, err := fs.Reader(fmt.Sprintf("%s-state.json", c.path))
312 if err != nil {
313 return state{}, err
314 }
315 defer r.Close()
316 var ret state
317 if err := json.NewDecoder(r).Decode(&ret); err != nil {
318 return state{}, err
319 }
320 return ret, err
giod28f83c2024-08-15 10:53:40 +0400321}
322
323func (c *repoClient) writeState(fs soft.RepoFS) error {
324 w, err := fs.Writer(fmt.Sprintf("%s-state.json", c.path))
325 if err != nil {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400326 return err
327 }
giod28f83c2024-08-15 10:53:40 +0400328 defer w.Close()
gioc76baed2024-08-19 22:04:57 +0400329 if err := json.NewEncoder(w).Encode(state{c.preOpenPorts, c.blocklist, c.reserve}); err != nil {
giod28f83c2024-08-15 10:53:40 +0400330 return err
331 }
332 return err
333}
334
335func (c *repoClient) readRelease(fs soft.RepoFS) (map[string]any, error) {
336 ret := map[string]any{}
337 if err := soft.ReadYaml(fs, c.path, &ret); err != nil {
338 return nil, err
339 }
340 return ret, nil
341}
342
343func (c *repoClient) writeRelease(fs soft.RepoFS, rel map[string]any) error {
344 return soft.WriteYaml(fs, c.path, rel)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400345}
346
347type server struct {
giod28f83c2024-08-15 10:53:40 +0400348 s *http.Server
349 r *http.ServeMux
350 client client
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400351}
352
353func newServer(port int, client client) *server {
354 r := http.NewServeMux()
355 s := &http.Server{
356 Addr: fmt.Sprintf(":%d", port),
357 Handler: r,
358 }
giod28f83c2024-08-15 10:53:40 +0400359 return &server{s, r, client}
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400360}
361
362func (s *server) Start() error {
gioefa0ed42024-06-13 12:31:43 +0400363 s.r.HandleFunc("/api/reserve", s.handleReserve)
giocdfa3722024-06-13 20:10:14 +0400364 s.r.HandleFunc("/api/allocate", s.handleAllocate)
365 s.r.HandleFunc("/api/remove", s.handleRemove)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400366 if err := s.s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
367 return err
368 }
369 return nil
370}
371
372func (s *server) Close() error {
373 return s.s.Close()
374}
375
376type allocateReq struct {
377 Protocol string `json:"protocol"`
378 SourcePort int `json:"sourcePort"`
379 TargetService string `json:"targetService"`
380 TargetPort int `json:"targetPort"`
giobd7ab0b2024-06-17 12:55:17 +0400381 Secret string `json:"secret"`
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400382}
383
giocdfa3722024-06-13 20:10:14 +0400384type removeReq struct {
385 Protocol string `json:"protocol"`
386 SourcePort int `json:"sourcePort"`
387 TargetService string `json:"targetService"`
388 TargetPort int `json:"targetPort"`
389}
390
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400391func extractAllocateReq(r io.Reader) (allocateReq, error) {
392 var req allocateReq
393 if err := json.NewDecoder(r).Decode(&req); err != nil {
394 return allocateReq{}, err
395 }
396 req.Protocol = strings.ToLower(req.Protocol)
397 if req.Protocol != "tcp" && req.Protocol != "udp" {
398 return allocateReq{}, fmt.Errorf("Unexpected protocol %s", req.Protocol)
399 }
400 return req, nil
401}
402
giocdfa3722024-06-13 20:10:14 +0400403func extractRemoveReq(r io.Reader) (removeReq, error) {
404 var req removeReq
405 if err := json.NewDecoder(r).Decode(&req); err != nil {
406 return removeReq{}, err
407 }
408 req.Protocol = strings.ToLower(req.Protocol)
409 if req.Protocol != "tcp" && req.Protocol != "udp" {
410 return removeReq{}, fmt.Errorf("Unexpected protocol %s", req.Protocol)
411 }
412 return req, nil
413}
414
gioefa0ed42024-06-13 12:31:43 +0400415type reserveResp struct {
416 Port int `json:"port"`
417 Secret string `json:"secret"`
418}
419
gioa344a2a2024-08-16 17:13:48 +0400420func extractField(data map[string]any, path string) (any, error) {
421 var val any = data
giod28f83c2024-08-15 10:53:40 +0400422 for _, i := range strings.Split(path, ".") {
gioa344a2a2024-08-16 17:13:48 +0400423 valM, ok := val.(map[string]any)
424 if !ok {
425 return nil, fmt.Errorf("expected map")
426 }
427 val, ok = valM[i]
giod28f83c2024-08-15 10:53:40 +0400428 if !ok {
429 return nil, fmt.Errorf("%s not found", i)
430 }
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400431 }
gioa344a2a2024-08-16 17:13:48 +0400432 return val, nil
433}
434
435func extractPorts(data map[string]any, path string) (map[string]any, error) {
436 ret, err := extractField(data, path)
437 if err != nil {
438 return nil, err
439 }
440 retM, ok := ret.(map[string]any)
441 if !ok {
442 return nil, fmt.Errorf("expected map")
443 }
444 return retM, nil
445}
446
447func extractString(data map[string]any, path string) (string, error) {
448 ret, err := extractField(data, path)
449 if err != nil {
450 return "", err
451 }
452 retS, ok := ret.(string)
453 if !ok {
454 return "", fmt.Errorf("expected map")
455 }
456 return retS, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400457}
458
giod28f83c2024-08-15 10:53:40 +0400459func addPort(pm map[string]any, sourcePort int, targetService string, targetPort int) error {
460 sourcePortStr := strconv.Itoa(sourcePort)
461 if _, ok := pm[sourcePortStr]; ok || sourcePort == 80 || sourcePort == 443 || sourcePort == 22 {
462 return fmt.Errorf("port %d is already taken", sourcePort)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400463 }
giod28f83c2024-08-15 10:53:40 +0400464 pm[sourcePortStr] = fmt.Sprintf("%s:%d", targetService, targetPort)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400465 return nil
466}
467
giod28f83c2024-08-15 10:53:40 +0400468func removePort(pm map[string]any, port int) error {
469 sourcePortStr := strconv.Itoa(port)
giocdfa3722024-06-13 20:10:14 +0400470 if _, ok := pm[sourcePortStr]; !ok {
giod28f83c2024-08-15 10:53:40 +0400471 return fmt.Errorf("port %d is not open to remove", port)
giocdfa3722024-06-13 20:10:14 +0400472 }
473 delete(pm, sourcePortStr)
474 return nil
475}
476
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400477func (s *server) handleAllocate(w http.ResponseWriter, r *http.Request) {
478 if r.Method != http.MethodPost {
479 http.Error(w, "only post method is supported", http.StatusBadRequest)
480 return
481 }
482 req, err := extractAllocateReq(r.Body)
483 if err != nil {
giod28f83c2024-08-15 10:53:40 +0400484 fmt.Println(err.Error())
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400485 http.Error(w, err.Error(), http.StatusBadRequest)
486 return
487 }
giod28f83c2024-08-15 10:53:40 +0400488 if err := s.client.AddPortForwarding(
489 req.Protocol,
490 req.SourcePort,
491 req.Secret,
492 fmt.Sprintf("%s:%d", req.TargetService, req.TargetPort),
493 ); err != nil {
494 fmt.Println(err.Error())
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400495 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400496 }
497}
498
gioefa0ed42024-06-13 12:31:43 +0400499func (s *server) handleReserve(w http.ResponseWriter, r *http.Request) {
500 if r.Method != http.MethodPost {
501 http.Error(w, "only post method is supported", http.StatusBadRequest)
502 return
503 }
gioefa0ed42024-06-13 12:31:43 +0400504 var port int
giod28f83c2024-08-15 10:53:40 +0400505 var secret string
506 var err error
507 if port, secret, err = s.client.ReservePort(); err != nil {
508 fmt.Println(err.Error())
gioefa0ed42024-06-13 12:31:43 +0400509 http.Error(w, err.Error(), http.StatusInternalServerError)
510 return
511 }
gioefa0ed42024-06-13 12:31:43 +0400512 go func() {
513 time.Sleep(30 * time.Minute)
giod28f83c2024-08-15 10:53:40 +0400514 s.client.ReleaseReservedPort(port)
gioefa0ed42024-06-13 12:31:43 +0400515 }()
giod28f83c2024-08-15 10:53:40 +0400516 if err := json.NewEncoder(w).Encode(reserveResp{port, secret}); err != nil {
517 fmt.Println(err.Error())
gioefa0ed42024-06-13 12:31:43 +0400518 http.Error(w, err.Error(), http.StatusInternalServerError)
519 return
520 }
521}
522
giocdfa3722024-06-13 20:10:14 +0400523func (s *server) handleRemove(w http.ResponseWriter, r *http.Request) {
524 if r.Method != http.MethodPost {
525 http.Error(w, "only post method is supported", http.StatusBadRequest)
526 return
527 }
528 req, err := extractRemoveReq(r.Body)
529 if err != nil {
giod28f83c2024-08-15 10:53:40 +0400530 fmt.Println(err.Error())
giocdfa3722024-06-13 20:10:14 +0400531 http.Error(w, err.Error(), http.StatusBadRequest)
532 return
533 }
giod28f83c2024-08-15 10:53:40 +0400534 if err := s.client.RemovePortForwarding(req.Protocol, req.SourcePort); err != nil {
535 fmt.Println(err.Error())
giocdfa3722024-06-13 20:10:14 +0400536 http.Error(w, err.Error(), http.StatusInternalServerError)
537 return
538 }
giocdfa3722024-06-13 20:10:14 +0400539}
540
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400541// TODO(gio): deduplicate
gioe72b54f2024-04-22 10:44:41 +0400542func createRepoClient(addr string, keyPath string) (soft.RepoIO, error) {
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400543 sshKey, err := os.ReadFile(keyPath)
544 if err != nil {
545 return nil, err
546 }
547 signer, err := ssh.ParsePrivateKey(sshKey)
548 if err != nil {
549 return nil, err
550 }
551 repoAddr, err := soft.ParseRepositoryAddress(addr)
552 if err != nil {
553 return nil, err
554 }
555 repo, err := soft.CloneRepository(repoAddr, signer)
556 if err != nil {
557 return nil, err
558 }
gioff2a29a2024-05-01 17:06:42 +0400559 return soft.NewRepoIO(repo, signer)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400560}
561
gioc76baed2024-08-19 22:04:57 +0400562type SecretGenerator func() (string, error)
563
Davit Tabidze6bf29832024-06-17 16:51:54 +0400564func generateSecret() (string, error) {
565 b := make([]byte, secretLength)
566 _, err := rand.Read(b)
567 if err != nil {
568 return "", fmt.Errorf("error generating secret: %v", err)
569 }
giob1c4e542024-07-15 12:10:52 +0400570 return base64.StdEncoding.EncodeToString(b), nil
gioefa0ed42024-06-13 12:31:43 +0400571}
572
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400573func main() {
574 flag.Parse()
575 repo, err := createRepoClient(*repoAddr, *sshKey)
576 if err != nil {
577 log.Fatal(err)
578 }
giod28f83c2024-08-15 10:53:40 +0400579 c, err := newRepoClient(
580 repo,
581 *ingressNginxPath,
582 *minPreOpenPorts,
583 *preOpenPortsBatchSize,
gioc76baed2024-08-19 22:04:57 +0400584 generateSecret,
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400585 )
giod28f83c2024-08-15 10:53:40 +0400586 if err != nil {
587 log.Fatal(err)
588 }
589 s := newServer(*port, c)
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400590 log.Fatal(s.Start())
591}