package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" "proxmox-router/internal/aerotun" "proxmox-router/internal/auth" "proxmox-router/internal/vpn" ) func aeroRequest(method, path, body string, role auth.Role) *http.Request { r := httptest.NewRequest(method, path, strings.NewReader(body)) return r.WithContext(context.WithValue(r.Context(), sessionContextKey, &auth.Session{Role: role})) } func TestAeroTUNSecretRedactionAndEdit(t *testing.T) { m := aerotun.NewManagerWithPath(filepath.Join(t.TempDir(), "aerotun.json")) defer m.StopAll() key := strings.Repeat("12", 32) saved, err := m.Save(aerotun.TunnelInstance{Name: "atest", Mode: "server", LocalAddress: "10.99.0.1/24", PSK: key, Disabled: true}) if err != nil { t.Fatal(err) } s := &Server{vpnMgr: &vpn.Manager{AeroTUN: m}} w := httptest.NewRecorder() s.handleListAeroTUNTunnels(w, aeroRequest("GET", "/api/vpn/aerotun/tunnels", "", auth.RoleRead)) if strings.Contains(w.Body.String(), key) || strings.Contains(w.Body.String(), `"psk":`) { t.Fatal("list disclosed PSK") } var views []aerotun.TunnelInstance if err = json.Unmarshal(w.Body.Bytes(), &views); err != nil || !views[0].HasPSK { t.Fatal("missing safe key presence indicator", err) } saved.PSK = "" saved.Comment = "edited without revealing secret" body, _ := json.Marshal(saved) w = httptest.NewRecorder() s.handleSaveAeroTUNTunnel(w, aeroRequest("POST", "/api/vpn/aerotun/tunnels", string(body), auth.RoleFull)) if w.Code != 200 || strings.Contains(w.Body.String(), key) { t.Fatalf("save failed or disclosed PSK: %d", w.Code) } got, _ := m.Get(saved.ID) if got.PSK != key { t.Fatal("redacted edit erased the stored key") } for _, role := range []auth.Role{auth.RoleRead, auth.RoleWrite} { w = httptest.NewRecorder() s.handleSaveAeroTUNTunnel(w, aeroRequest("POST", "/api/vpn/aerotun/tunnels", string(body), role)) if w.Code != 403 { t.Fatal("non-administrator modified tunnel") } w = httptest.NewRecorder() s.handleGenerateAeroTUNKey(w, aeroRequest("GET", "/api/vpn/aerotun/genkey", "", role)) if w.Code != 403 { t.Fatal("non-administrator generated key") } } } func TestAeroTUNPrivateIdentityRedacted(t *testing.T) { private, public, err := aerotun.GenerateIdentity() if err != nil { t.Fatal(err) } view := aerotun.TunnelInstance{PSK: strings.Repeat("ab", 32), PrivateKey: private, PublicKey: public}.PublicView() encoded, _ := json.Marshal(view) if strings.Contains(string(encoded), private) || !view.HasPrivateKey || view.PublicKey != public { t.Fatal("private identity exposed or public identity lost") } }