AppManager: Clean up VPN node and auth keys upon app removal
Change-Id: Ie76278556247d16806ba81286621adca973e3f6e
diff --git a/core/headscale/client.go b/core/headscale/client.go
index ce82291..18c37df 100644
--- a/core/headscale/client.go
+++ b/core/headscale/client.go
@@ -1,9 +1,12 @@
package main
import (
+ "bytes"
+ "encoding/json"
"errors"
"fmt"
"os/exec"
+ "strconv"
"strings"
)
@@ -33,21 +36,81 @@
// TODO(giolekva): make expiration configurable, and auto-refresh
cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "create", "--reusable", "--expiration", "365d")
out, err := cmd.Output()
+ fmt.Println(string(out))
if err != nil {
return "", err
}
- fmt.Println(string(out))
return extractLastLine(string(out))
}
+func (c *client) expirePreAuthKey(user, authKey string) error {
+ cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "expire", authKey)
+ out, err := cmd.Output()
+ fmt.Println(string(out))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (c *client) expireUserNode(user, node string) error {
+ id, err := c.getNodeId(user, node)
+ if err != nil {
+ return err
+ }
+ cmd := exec.Command("headscale", c.config, "node", "expire", "--identifier", id)
+ out, err := cmd.Output()
+ fmt.Println(string(out))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (c *client) removeUserNode(user, node string) error {
+ id, err := c.getNodeId(user, node)
+ if err != nil {
+ return err
+ }
+ cmd := exec.Command("headscale", c.config, "node", "delete", "--identifier", id, "--force")
+ out, err := cmd.Output()
+ fmt.Println(string(out))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (c *client) enableRoute(id string) error {
- // TODO(giolekva): make expiration configurable, and auto-refresh
cmd := exec.Command("headscale", c.config, "routes", "enable", "-r", id)
out, err := cmd.Output()
fmt.Println(string(out))
return err
}
+type nodeInfo struct {
+ Id int `json:"id"`
+ Name string `json:"name"`
+}
+
+func (c *client) getNodeId(user, node string) (string, error) {
+ cmd := exec.Command("headscale", c.config, "--user", user, "node", "list", "-o", "json")
+ out, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ var nodes []nodeInfo
+ if err := json.NewDecoder(bytes.NewReader(out)).Decode(&nodes); err != nil {
+ return "", err
+ }
+ for _, n := range nodes {
+ if n.Name == node {
+ return strconv.Itoa(n.Id), nil
+ }
+ }
+ return "", fmt.Errorf("not found")
+}
+
func extractLastLine(s string) (string, error) {
items := strings.Split(s, "\n")
for i := len(items) - 1; i >= 0; i-- {
diff --git a/core/headscale/main.go b/core/headscale/main.go
index da0ff6c..698d9d2 100644
--- a/core/headscale/main.go
+++ b/core/headscale/main.go
@@ -88,6 +88,9 @@
r := mux.NewRouter()
r.HandleFunc("/sync-users", s.handleSyncUsers).Methods(http.MethodGet)
r.HandleFunc("/user/{user}/preauthkey", s.createReusablePreAuthKey).Methods(http.MethodPost)
+ r.HandleFunc("/user/{user}/preauthkey", s.expireReusablePreAuthKey).Methods(http.MethodDelete)
+ r.HandleFunc("/user/{user}/node/{node}/expire", s.expireUserNode).Methods(http.MethodPost)
+ r.HandleFunc("/user/{user}/node/{node}", s.removeUserNode).Methods(http.MethodDelete)
r.HandleFunc("/user", s.createUser).Methods(http.MethodPost)
r.HandleFunc("/routes/{id}/enable", s.enableRoute).Methods(http.MethodPost)
go func() {
@@ -132,6 +135,63 @@
}
}
+type expirePreAuthKeyReq struct {
+ AuthKey string `json:"authKey"`
+}
+
+func (s *server) expireReusablePreAuthKey(w http.ResponseWriter, r *http.Request) {
+ user, ok := mux.Vars(r)["user"]
+ if !ok {
+ http.Error(w, "no user", http.StatusBadRequest)
+ return
+ }
+ var req expirePreAuthKeyReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ if err := s.client.expirePreAuthKey(user, req.AuthKey); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (s *server) expireUserNode(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("expire node")
+ user, ok := mux.Vars(r)["user"]
+ if !ok {
+ http.Error(w, "no user", http.StatusBadRequest)
+ return
+ }
+ node, ok := mux.Vars(r)["node"]
+ if !ok {
+ http.Error(w, "no user", http.StatusBadRequest)
+ return
+ }
+ if err := s.client.expireUserNode(user, node); err != nil {
+ fmt.Println(err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (s *server) removeUserNode(w http.ResponseWriter, r *http.Request) {
+ user, ok := mux.Vars(r)["user"]
+ if !ok {
+ http.Error(w, "no user", http.StatusBadRequest)
+ return
+ }
+ node, ok := mux.Vars(r)["node"]
+ if !ok {
+ http.Error(w, "no user", http.StatusBadRequest)
+ return
+ }
+ if err := s.client.removeUserNode(user, node); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
func (s *server) handleSyncUsers(_ http.ResponseWriter, _ *http.Request) {
go s.syncUsers()
}