AppManager: Clean up VPN node and auth keys upon app removal
Change-Id: Ie76278556247d16806ba81286621adca973e3f6e
diff --git a/core/installer/app.go b/core/installer/app.go
index 3a16abe..91f290f 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -202,7 +202,7 @@
networks []Network,
values map[string]any,
charts map[string]helmv2.HelmChartTemplateSpec,
- vpnKeyGen VPNAuthKeyGenerator,
+ vpnKeyGen VPNAPIClient,
) (EnvAppRendered, error)
}
@@ -459,7 +459,7 @@
networks []Network,
values map[string]any,
charts map[string]helmv2.HelmChartTemplateSpec,
- vpnKeyGen VPNAuthKeyGenerator,
+ vpnKeyGen VPNAPIClient,
) (EnvAppRendered, error) {
derived, err := deriveValues(values, values, a.Schema(), networks, vpnKeyGen)
if err != nil {
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 013c05d..d31d9ff 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -30,13 +30,13 @@
var ErrorNotFound = errors.New("not found")
type AppManager struct {
- l sync.Locker
- repoIO soft.RepoIO
- nsc NamespaceCreator
- jc JobCreator
- hf HelmFetcher
- vpnKeyGen VPNAuthKeyGenerator
- appDirRoot string
+ l sync.Locker
+ repoIO soft.RepoIO
+ nsc NamespaceCreator
+ jc JobCreator
+ hf HelmFetcher
+ vpnAPIClient VPNAPIClient
+ appDirRoot string
}
func NewAppManager(
@@ -44,7 +44,7 @@
nsc NamespaceCreator,
jc JobCreator,
hf HelmFetcher,
- vpnKeyGen VPNAuthKeyGenerator,
+ vpnKeyGen VPNAPIClient,
appDirRoot string,
) (*AppManager, error) {
return &AppManager{
@@ -468,7 +468,7 @@
RepoAddr: m.repoIO.FullAddress(),
AppDir: appDir,
}
- rendered, err := app.Render(release, env, networks, values, nil, m.vpnKeyGen)
+ rendered, err := app.Render(release, env, networks, values, nil, m.vpnAPIClient)
if err != nil {
return ReleaseResources{}, err
}
@@ -500,7 +500,7 @@
if o.FetchContainerImages {
release.ImageRegistry = imageRegistry
}
- rendered, err = app.Render(release, env, networks, values, localCharts, m.vpnKeyGen)
+ rendered, err = app.Render(release, env, networks, values, localCharts, m.vpnAPIClient)
if err != nil {
return ReleaseResources{}, err
}
@@ -593,7 +593,7 @@
if err != nil {
return ReleaseResources{}, err
}
- rendered, err := app.Render(config.Release, env, networks, values, renderedCfg.LocalCharts, m.vpnKeyGen)
+ rendered, err := app.Render(config.Release, env, networks, values, renderedCfg.LocalCharts, m.vpnAPIClient)
if err != nil {
return ReleaseResources{}, err
}
@@ -613,14 +613,14 @@
if err := m.repoIO.Pull(); err != nil {
return err
}
- var portForward []PortForward
+ var cfg renderedInstance
if _, err := m.repoIO.Do(func(r soft.RepoFS) (string, error) {
instanceDir := filepath.Join(m.appDirRoot, instanceId)
renderedCfg, err := readRendered(m.repoIO, filepath.Join(instanceDir, "rendered.json"))
if err != nil {
return "", err
}
- portForward = renderedCfg.PortForward
+ cfg = renderedCfg
r.RemoveDir(instanceDir)
kustPath := filepath.Join(m.appDirRoot, "kustomization.yaml")
kust, err := soft.ReadKustomization(r, kustPath)
@@ -633,9 +633,22 @@
}); err != nil {
return err
}
- if err := closePorts(portForward); err != nil {
+ if err := closePorts(cfg.PortForward); err != nil {
return err
}
+ for vmName, vmCfg := range cfg.Out.VM {
+ if vmCfg.VPN.Enabled {
+ if err := m.vpnAPIClient.ExpireNode(vmCfg.Username, vmName); err != nil {
+ return err
+ }
+ if err := m.vpnAPIClient.ExpireKey(vmCfg.Username, vmCfg.VPN.AuthKey); err != nil {
+ return err
+ }
+ if err := m.vpnAPIClient.RemoveNode(vmCfg.Username, vmName); err != nil {
+ return err
+ }
+ }
+ }
return nil
}
@@ -951,6 +964,19 @@
type renderedInstance struct {
LocalCharts map[string]helmv2.HelmChartTemplateSpec `json:"localCharts"`
PortForward []PortForward `json:"portForward"`
+ Out outRendered `json:"out"`
+}
+
+type outRendered struct {
+ VM map[string]vmRendered `json:"vm"`
+}
+
+type vmRendered struct {
+ Username string `json:"username"`
+ VPN struct {
+ Enabled bool `json:"enabled"`
+ AuthKey string `json:"authKey"`
+ } `json:"vpn"`
}
func readRendered(fs soft.RepoFS, path string) (renderedInstance, error) {
diff --git a/core/installer/derived.go b/core/installer/derived.go
index 508cd3e..d99f02b 100644
--- a/core/installer/derived.go
+++ b/core/installer/derived.go
@@ -69,7 +69,7 @@
values any,
schema Schema,
networks []Network,
- vpnKeyGen VPNAuthKeyGenerator,
+ vpnKeyGen VPNAPIClient,
) (map[string]any, error) {
ret := make(map[string]any)
for _, f := range schema.Fields() {
@@ -100,7 +100,7 @@
return nil, fmt.Errorf("could not resolve username: %+v %s %+v", def.Meta(), v, root)
}
}
- authKey, err := vpnKeyGen.Generate(username)
+ authKey, err := vpnKeyGen.GenerateAuthKey(username)
if err != nil {
return nil, err
}
diff --git a/core/installer/derived_test.go b/core/installer/derived_test.go
index 7a34154..8f638b3 100644
--- a/core/installer/derived_test.go
+++ b/core/installer/derived_test.go
@@ -6,10 +6,22 @@
type testKeyGen struct{}
-func (g testKeyGen) Generate(username string) (string, error) {
+func (g testKeyGen) GenerateAuthKey(username string) (string, error) {
return username, nil
}
+func (g testKeyGen) ExpireKey(username, key string) error {
+ return nil
+}
+
+func (g testKeyGen) ExpireNode(username, node string) error {
+ return nil
+}
+
+func (g testKeyGen) RemoveNode(username, node string) error {
+ return nil
+}
+
func TestDeriveVPNAuthKey(t *testing.T) {
schema := structSchema{
"input",
diff --git a/core/installer/vpn.go b/core/installer/vpn.go
index 4739bf2..01161df 100644
--- a/core/installer/vpn.go
+++ b/core/installer/vpn.go
@@ -2,25 +2,34 @@
import (
"bytes"
+ "encoding/json"
"errors"
"fmt"
"io"
"net/http"
+ "net/url"
)
-type VPNAuthKeyGenerator interface {
- Generate(username string) (string, error)
+type VPNAPIClient interface {
+ GenerateAuthKey(username string) (string, error)
+ ExpireKey(username, key string) error
+ ExpireNode(username, node string) error
+ RemoveNode(username, node string) error
}
type headscaleAPIClient struct {
+ c *http.Client
apiAddr string
}
-func NewHeadscaleAPIClient(apiAddr string) VPNAuthKeyGenerator {
- return &headscaleAPIClient{apiAddr}
+func NewHeadscaleAPIClient(apiAddr string) VPNAPIClient {
+ return &headscaleAPIClient{
+ &http.Client{},
+ apiAddr,
+ }
}
-func (g *headscaleAPIClient) Generate(username string) (string, error) {
+func (g *headscaleAPIClient) GenerateAuthKey(username string) (string, error) {
resp, err := http.Post(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username), "application/json", nil)
if err != nil {
return "", err
@@ -32,3 +41,70 @@
}
return buf.String(), nil
}
+
+type expirePreAuthKeyReq struct {
+ AuthKey string `json:"authKey"`
+}
+
+func (g *headscaleAPIClient) ExpireKey(username, key string) error {
+ addr, err := url.Parse(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username))
+ if err != nil {
+ return err
+ }
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(expirePreAuthKeyReq{key}); err != nil {
+ return err
+ }
+ resp, err := g.c.Do(&http.Request{
+ URL: addr,
+ Method: http.MethodDelete,
+ Body: io.NopCloser(&buf),
+ })
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ return errors.New(buf.String())
+ }
+ return nil
+}
+
+func (g *headscaleAPIClient) ExpireNode(username, node string) error {
+ resp, err := g.c.Post(
+ fmt.Sprintf("%s/user/%s/node/%s/expire", g.apiAddr, username, node),
+ "text/plain",
+ nil,
+ )
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ return errors.New(buf.String())
+ }
+ return nil
+}
+
+func (g *headscaleAPIClient) RemoveNode(username, node string) error {
+ addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s", g.apiAddr, username, node))
+ if err != nil {
+ return err
+ }
+ resp, err := g.c.Do(&http.Request{
+ URL: addr,
+ Method: http.MethodDelete,
+ Body: nil,
+ })
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ return errors.New(buf.String())
+ }
+ return nil
+}
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 57de8b2..9f60449 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -103,7 +103,7 @@
env installer.EnvConfig
nsc installer.NamespaceCreator
jc installer.JobCreator
- vpnKeyGen installer.VPNAuthKeyGenerator
+ vpnKeyGen installer.VPNAPIClient
workers map[string]map[string]struct{}
appConfigs map[string]appConfig
tmplts dodoAppTmplts
@@ -135,7 +135,7 @@
envAppManagerAddr string,
nsc installer.NamespaceCreator,
jc installer.JobCreator,
- vpnKeyGen installer.VPNAuthKeyGenerator,
+ vpnKeyGen installer.VPNAPIClient,
env installer.EnvConfig,
external bool,
fetchUsersAddr string,