Show routes, TLS certs, and port-forwarding on services page
- Add Routes model to services UI (paths, headers, IP whitelisting per route) - Show TLS cert info per service with inline provision/renew actions - Remove TLS Certificates section from dashboard (now on services page) - Make gateway router port list dynamic from config + VPN state - Add TODO for header validation in HAProxy config generation
This commit is contained in:
@@ -68,7 +68,7 @@ func NewAPI(dataDir, version string, allowedOrigins []string) (*API, error) {
|
||||
version: version,
|
||||
allowedOrigins: allowedOrigins,
|
||||
config: configMgr,
|
||||
secrets: secrets.NewManager(),
|
||||
secrets: secrets.NewManager(filepath.Join(dataDir, "secrets.yaml")),
|
||||
dnsmasq: dnsmasq.NewConfigGenerator(dnsmasqConfigPath),
|
||||
haproxy: haproxy.NewManager(haproxyConfigPath),
|
||||
nftables: nftables.NewManager(nftablesRulesPath),
|
||||
@@ -327,26 +327,12 @@ func (api *API) UpdateGlobalConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// GetGlobalSecrets returns the global secrets (redacted by default)
|
||||
func (api *API) GetGlobalSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
data, err := os.ReadFile(secretsPath)
|
||||
secretsMap, err := api.secrets.GetAll()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
respondJSON(w, http.StatusOK, map[string]any{})
|
||||
return
|
||||
}
|
||||
respondError(w, http.StatusInternalServerError, "Failed to read secrets")
|
||||
return
|
||||
}
|
||||
|
||||
var secretsMap map[string]any
|
||||
if err := yaml.Unmarshal(data, &secretsMap); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to parse secrets")
|
||||
return
|
||||
}
|
||||
if secretsMap == nil {
|
||||
secretsMap = map[string]any{}
|
||||
}
|
||||
|
||||
showRaw := r.URL.Query().Get("raw") == "true"
|
||||
if !showRaw {
|
||||
redactSecrets(secretsMap)
|
||||
@@ -357,8 +343,6 @@ func (api *API) GetGlobalSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// UpdateGlobalSecrets updates the global secrets
|
||||
func (api *API) UpdateGlobalSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to read request body")
|
||||
@@ -371,36 +355,7 @@ func (api *API) UpdateGlobalSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
existingContent, err := storage.ReadFile(secretsPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to read existing secrets")
|
||||
return
|
||||
}
|
||||
|
||||
var existing map[string]any
|
||||
if len(existingContent) > 0 {
|
||||
if err := yaml.Unmarshal(existingContent, &existing); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to parse existing secrets")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
existing = make(map[string]any)
|
||||
}
|
||||
|
||||
for key, value := range updates {
|
||||
existing[key] = value
|
||||
}
|
||||
|
||||
yamlContent, err := yaml.Marshal(existing)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to marshal YAML")
|
||||
return
|
||||
}
|
||||
|
||||
lockPath := secretsPath + ".lock"
|
||||
if err := storage.WithLock(lockPath, func() error {
|
||||
return storage.WriteFile(secretsPath, yamlContent, 0600)
|
||||
}); err != nil {
|
||||
if err := api.secrets.MergeUpdate(updates); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to write secrets")
|
||||
return
|
||||
}
|
||||
@@ -488,45 +443,19 @@ func redactSecrets(m map[string]any) {
|
||||
|
||||
// getCloudflareToken reads the Cloudflare API token from global secrets
|
||||
func (api *API) getCloudflareToken() string {
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
data, err := os.ReadFile(secretsPath)
|
||||
token, err := api.secrets.GetSecret("cloudflare.apiToken")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var secretsMap map[string]any
|
||||
if err := yaml.Unmarshal(data, &secretsMap); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
cloudflare, ok := secretsMap["cloudflare"].(map[string]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
token, _ := cloudflare["apiToken"].(string)
|
||||
return token
|
||||
}
|
||||
|
||||
// getCloudflareZoneID reads the Cloudflare Zone ID from global secrets
|
||||
func (api *API) getCloudflareZoneID() string {
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
data, err := os.ReadFile(secretsPath)
|
||||
zoneID, err := api.secrets.GetSecret("cloudflare.zoneId")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var secretsMap map[string]any
|
||||
if err := yaml.Unmarshal(data, &secretsMap); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
cloudflare, ok := secretsMap["cloudflare"].(map[string]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
zoneID, _ := cloudflare["zoneId"].(string)
|
||||
return zoneID
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package v1
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type cloudflareZone struct {
|
||||
@@ -71,8 +70,7 @@ func (api *API) CloudflareSetZoneID(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
secretsPath := filepath.Join(api.dataDir, "secrets.yaml")
|
||||
if err := api.secrets.SetSecret(secretsPath, "cloudflare.zoneId", req.ZoneID); err != nil {
|
||||
if err := api.secrets.SetSecret("cloudflare.zoneId", req.ZoneID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to save zone ID")
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user