158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
package v1
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/wild-cloud/wild-central/internal/config"
|
|
"github.com/wild-cloud/wild-central/internal/wireguard"
|
|
)
|
|
|
|
// VpnStatus returns the current WireGuard interface status.
|
|
func (api *API) VpnStatus(w http.ResponseWriter, r *http.Request) {
|
|
status, err := api.vpn.GetStatus()
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get VPN status: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
// VpnGetConfig returns the server interface configuration and public key.
|
|
func (api *API) VpnGetConfig(w http.ResponseWriter, r *http.Request) {
|
|
cfg, err := api.vpn.GetConfig()
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get VPN config: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]any{
|
|
"enabled": cfg.Enabled,
|
|
"listenPort": cfg.ListenPort,
|
|
"address": cfg.Address,
|
|
"endpoint": cfg.Endpoint,
|
|
"dns": cfg.DNS,
|
|
"lanCIDR": cfg.LanCIDR,
|
|
"publicKey": api.vpn.GetPublicKey(),
|
|
})
|
|
}
|
|
|
|
// VpnUpdateConfig updates the server interface configuration.
|
|
func (api *API) VpnUpdateConfig(w http.ResponseWriter, r *http.Request) {
|
|
var req wireguard.Config
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, fmt.Sprintf("Invalid request: %v", err))
|
|
return
|
|
}
|
|
if req.ListenPort == 0 {
|
|
req.ListenPort = 51820
|
|
}
|
|
if err := api.vpn.SaveConfig(&req); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to save VPN config: %v", err))
|
|
return
|
|
}
|
|
// Resync nftables so the VPN listen port is automatically allowed/removed
|
|
if globalCfg, err := config.LoadState(api.statePath()); err == nil {
|
|
go api.syncNftablesOnly(globalCfg)
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]string{"message": "VPN configuration saved"})
|
|
}
|
|
|
|
// VpnGenerateKeypair generates a new server keypair.
|
|
func (api *API) VpnGenerateKeypair(w http.ResponseWriter, r *http.Request) {
|
|
if err := api.vpn.GenerateKeypair(); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate keypair: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"message": "Server keypair generated",
|
|
"publicKey": api.vpn.GetPublicKey(),
|
|
})
|
|
}
|
|
|
|
// VpnApply writes the wg0.conf and brings the WireGuard interface up.
|
|
func (api *API) VpnApply(w http.ResponseWriter, r *http.Request) {
|
|
if err := api.vpn.Apply(); err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to apply VPN config: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]string{"message": "WireGuard interface applied successfully"})
|
|
}
|
|
|
|
// VpnListPeers returns all configured peers (without private keys).
|
|
func (api *API) VpnListPeers(w http.ResponseWriter, r *http.Request) {
|
|
peers, err := api.vpn.ListPeers()
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list peers: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]any{"peers": redactPeers(peers)})
|
|
}
|
|
|
|
// VpnAddPeer adds a new peer, auto-generating a keypair and assigning an IP.
|
|
func (api *API) VpnAddPeer(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
|
|
respondError(w, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
peer, err := api.vpn.AddPeer(req.Name)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add peer: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusCreated, redactPeer(peer))
|
|
}
|
|
|
|
// VpnGetPeerConfig returns the wg-quick config text for a peer (used by clients and QR generation).
|
|
func (api *API) VpnGetPeerConfig(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
text, err := api.vpn.GeneratePeerConfigText(id)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate peer config: %v", err))
|
|
return
|
|
}
|
|
peer, err := api.vpn.GetPeer(id)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get peer: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]string{
|
|
"name": peer.Name,
|
|
"config": text,
|
|
})
|
|
}
|
|
|
|
// VpnDeletePeer removes a peer by ID.
|
|
func (api *API) VpnDeletePeer(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
if err := api.vpn.DeletePeer(id); err != nil {
|
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Failed to delete peer: %v", err))
|
|
return
|
|
}
|
|
respondJSON(w, http.StatusOK, map[string]string{"message": "Peer deleted"})
|
|
}
|
|
|
|
// redactPeers strips private keys from a peer list before sending to clients.
|
|
func redactPeers(peers []*wireguard.Peer) []map[string]any {
|
|
out := make([]map[string]any, len(peers))
|
|
for i, p := range peers {
|
|
out[i] = redactPeer(p)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func redactPeer(p *wireguard.Peer) map[string]any {
|
|
return map[string]any{
|
|
"id": p.ID,
|
|
"name": p.Name,
|
|
"publicKey": p.PublicKey,
|
|
"allowedIPs": p.AllowedIPs,
|
|
"createdAt": p.CreatedAt,
|
|
}
|
|
}
|