go-version #1

Merged
mans merged 3 commits from go-version into main 2025-08-20 13:25:27 +00:00
21 changed files with 497 additions and 742 deletions
Showing only changes of commit 41d9b96fa5 - Show all commits

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@
go.work go.work
# End of https://www.toptal.com/developers/gitignore/api/go # End of https://www.toptal.com/developers/gitignore/api/go
build/

Binary file not shown.

View File

@@ -1,11 +1,15 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"net"
"net/http" "net/http"
"os" "os"
"os/exec"
"git.mziesel.nl/mans/lg/internal/socket" "git.mziesel.nl/mans/lg/internal/socket"
) )
@@ -63,7 +67,7 @@ func isAuthenticated(r *http.Request) bool {
func ensureAuthenticatedMiddleware(next http.HandlerFunc) http.HandlerFunc { func ensureAuthenticatedMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) { if !isAuthenticated(r) {
http.Error(w, `{"error": "Unauthorized access"}`, http.StatusUnauthorized) http.Error(w, "Unauthorized access", http.StatusUnauthorized)
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@@ -77,14 +81,7 @@ func plainTextMiddleware(next http.HandlerFunc) http.HandlerFunc {
} }
} }
func jsonMiddleware(next http.HandlerFunc) http.HandlerFunc { func middlewareChain(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next(w, r)
}
}
func apiMiddlewareChain(h http.HandlerFunc) http.HandlerFunc {
return plainTextMiddleware(ensureAuthenticatedMiddleware(h)) return plainTextMiddleware(ensureAuthenticatedMiddleware(h))
} }
@@ -134,6 +131,87 @@ func birdCommandHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func createCommand(tool string, target string) (*exec.Cmd, error) {
commands := map[string][]string{
"ping": {"ping", "-4", "-c", "4", "--", target},
"ping6": {"ping", "-6", "-c", "4", "--", target},
"mtr": {"mtr", "--show-ips", "--aslookup", "--report-wide", "-c", "10", "-4", target},
"mtr6": {"mtr", "--show-ips", "--aslookup", "--report-wide", "-c", "10", "-6", target},
"traceroute": {"traceroute", "-n", "-4", "--", target},
"traceroute6": {"traceroute", "-n", "-6", "--", target},
}
if cmdArgs, exists := commands[tool]; exists {
path, err := exec.LookPath(cmdArgs[0])
if err != nil {
return nil, err
}
cmd := exec.Command(path, cmdArgs[1:]...)
return cmd, nil
}
return nil, errors.New("Command is not allowed")
}
func isValidTarget(target string) error {
addr, err := net.ResolveIPAddr("ip", target)
if err != nil {
return err
}
if addr.IP.IsPrivate() {
return errors.New("RFC 1918 and RFC 4193 address space is not allowed")
}
return nil
}
func executeStreaming(w http.ResponseWriter, r *http.Request) {
tool := r.URL.Query().Get("tool")
target := r.URL.Query().Get("target")
err := isValidTarget(target)
if err != nil {
http.Error(w, fmt.Sprintf("Bad Target: %s", err), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
cmd, err := createCommand(tool, target)
if err != nil {
http.Error(w, "Internal Server Error: failed to create command", http.StatusInternalServerError)
log.Printf("ERROR: Failed to create command: %s\n", err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
http.Error(w, "Internal Server Error: failed to open pipe to CMD output", http.StatusInternalServerError)
log.Printf("ERROR: Failed to open pipe to CMD output: %s\n", err)
return
}
cmd.Stderr = cmd.Stdout
err = cmd.Start()
if err != nil {
http.Error(w, "Internal Server Error: failed to start command", http.StatusInternalServerError)
log.Printf("ERROR: Failed to start command: %s\n", err)
return
}
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanLines)
_, pw, _ := os.Pipe()
cmd.Stdout = pw
cmd.Stdout = pw
for scanner.Scan() {
fmt.Fprintf(w, "%s\n", scanner.Bytes())
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}
func main() { func main() {
config := loadConfig() config := loadConfig()
formattedConfig, err := json.MarshalIndent(config, "", " ") formattedConfig, err := json.MarshalIndent(config, "", " ")
@@ -144,9 +222,10 @@ func main() {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/health", apiMiddlewareChain(healthHandler)) mux.HandleFunc("/health", middlewareChain(healthHandler))
mux.HandleFunc("/bird/status", apiMiddlewareChain(birdStatusHandler)) mux.HandleFunc("/bird/status", middlewareChain(birdStatusHandler))
mux.HandleFunc("/bird/command", apiMiddlewareChain(birdCommandHandler)) mux.HandleFunc("/bird/command", middlewareChain(birdCommandHandler))
mux.HandleFunc("/exec", middlewareChain(executeStreaming))
log.Printf("starting listner on %s:%d\n", config.Host, config.Port) log.Printf("starting listner on %s:%d\n", config.Host, config.Port)