400 lines
10 KiB
Go
400 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
)
|
|
|
|
type Config struct {
|
|
LG LGConfig `json:"lg"`
|
|
RateLimit RateLimitConfig `json:"rate_limit"`
|
|
Routers map[string]RouterConfig `json:"routers"`
|
|
}
|
|
|
|
type LGConfig struct {
|
|
Name string `json:"name"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
SharedSecret string `json:"shared_secret"`
|
|
}
|
|
|
|
type RateLimitConfig struct {
|
|
Requests int `json:"requests"`
|
|
TimeWindow int `json:"time_window"`
|
|
}
|
|
|
|
type RouterConfig struct {
|
|
Code string `json:"code"`
|
|
Description string `json:"description"`
|
|
IPv4 string `json:"ipv4"`
|
|
IPv6 string `json:"ipv6"`
|
|
Agent AgentConfig `json:"agent"`
|
|
}
|
|
|
|
type AgentConfig struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
SharedSecret string `json:"shared_secret"`
|
|
}
|
|
|
|
type BrowserRouterConfig struct {
|
|
Code string `json:"code"`
|
|
Description string `json:"description"`
|
|
IPv4 string `json:"ipv4"`
|
|
IPv6 string `json:"ipv6"`
|
|
}
|
|
|
|
var config Config
|
|
|
|
/*
|
|
Functions
|
|
*/
|
|
|
|
func loadConfig() Config {
|
|
// Default configuration
|
|
config = Config{}
|
|
|
|
// Determine the config file path
|
|
configPath := "/etc/lg-backend/backend.json"
|
|
if envPath := os.Getenv("LG_BACKEND_CONFIG_PATH"); envPath != "" {
|
|
configPath = envPath
|
|
}
|
|
|
|
// Open the configuration file
|
|
file, err := os.Open(configPath)
|
|
if err != nil {
|
|
log.Fatalf("Error opening config file: %v.", err)
|
|
return config
|
|
}
|
|
defer file.Close()
|
|
|
|
// Decode the JSON configuration
|
|
decoder := json.NewDecoder(file)
|
|
if err := decoder.Decode(&config); err != nil {
|
|
log.Fatalf("Error decoding config file: %v.", err)
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
func isAuthenticated(r *http.Request) bool {
|
|
if config.LG.SharedSecret == "" {
|
|
return true
|
|
}
|
|
|
|
authHeader := r.Header.Get("Authorization")
|
|
return authHeader == fmt.Sprintf("Bearer %s", config.LG.SharedSecret)
|
|
}
|
|
|
|
func ensureAuthenticatedMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if !isAuthenticated(r) {
|
|
http.Error(w, "Unauthorized access", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
}
|
|
}
|
|
|
|
func middlewareChain(h http.HandlerFunc) http.HandlerFunc {
|
|
return ensureAuthenticatedMiddleware(h)
|
|
}
|
|
|
|
func isValidIp(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 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
|
|
}
|
|
|
|
/*
|
|
Functions
|
|
*/
|
|
|
|
func getRouters() ([]BrowserRouterConfig, error) {
|
|
var routers []BrowserRouterConfig
|
|
|
|
// ensure no agent credentials get leaked
|
|
for _, r := range config.Routers {
|
|
router := BrowserRouterConfig{
|
|
Code: r.Code,
|
|
Description: r.Description,
|
|
IPv4: r.IPv4,
|
|
IPv6: r.IPv6,
|
|
}
|
|
routers = append(routers, router)
|
|
}
|
|
|
|
return routers, nil
|
|
}
|
|
|
|
func getRouterByCode(code string) (RouterConfig, error) {
|
|
for k, v := range config.Routers {
|
|
if k == code {
|
|
return v, nil
|
|
}
|
|
}
|
|
return RouterConfig{}, errors.New("not found")
|
|
}
|
|
|
|
func (r *RouterConfig) formatPath(path string) string {
|
|
ur := fmt.Sprintf("http://%s:%d%s", r.Agent.Host, r.Agent.Port, path)
|
|
return ur
|
|
}
|
|
|
|
func (r *RouterConfig) getStatus() ([]byte, error) {
|
|
res, err := http.Get(r.formatPath("/bird/status"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func (r *RouterConfig) getPrefix(input string, all bool) ([]byte, error) {
|
|
_, _, err := net.ParseCIDR(input)
|
|
if err != nil {
|
|
testIp := net.ParseIP(input)
|
|
if testIp == nil {
|
|
return nil, errors.New("Invalid Prefix")
|
|
}
|
|
}
|
|
|
|
fs := ""
|
|
if all {
|
|
fs = " all "
|
|
}
|
|
|
|
cmd := url.QueryEscape(fmt.Sprintf("show route %s for %s\n", fs, input))
|
|
res, err := http.Get(r.formatPath(fmt.Sprintf("/bird/command?c=%s", cmd)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func (r *RouterConfig) getProtocols(all bool) ([]byte, error) {
|
|
fs := ""
|
|
if all {
|
|
fs = " all "
|
|
}
|
|
|
|
cmd := url.QueryEscape(fmt.Sprintf("show protocols%s\n", fs))
|
|
res, err := http.Get(r.formatPath(fmt.Sprintf("/bird/command?c=%s", cmd)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
/*
|
|
Handlers
|
|
*/
|
|
|
|
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`{"status": "ok"}`))
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
func routersHandler(w http.ResponseWriter, r *http.Request) {
|
|
routers, err := getRouters()
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Internal Server Error: %s", err), http.StatusInternalServerError)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(routers); err != nil {
|
|
http.Error(w, fmt.Sprintf("Internal Server Error: failed to encode response: %s", err), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func routerStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
if code == "" {
|
|
http.Error(w, "Bad Request: no router selected", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
router, err := getRouterByCode(code)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed to lookup router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to lookup router: %s\n", err)
|
|
return
|
|
}
|
|
|
|
status, err := router.getStatus()
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed get router status", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to get router status: %s\n", err)
|
|
return
|
|
}
|
|
|
|
if _, err := w.Write(status); err != nil {
|
|
http.Error(w, "Internal Server Error: failed to write data to client", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to write data to client: %s\n", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func routerPrefixHandler(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
input := r.URL.Query().Get("prefix")
|
|
all := r.URL.Query().Get("all") != ""
|
|
|
|
if code == "" {
|
|
http.Error(w, "Bad Request: no router selected", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if input == "" {
|
|
http.Error(w, "Bad Request: no prefix supplied", http.StatusBadRequest)
|
|
return
|
|
}
|
|
router, err := getRouterByCode(code)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed to lookup router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to lookup router: %s\n", err)
|
|
return
|
|
}
|
|
// TODO: check if in the _all path handler
|
|
status, err := router.getPrefix(input, all)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed query router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to query router: %s\n", err)
|
|
return
|
|
}
|
|
|
|
if _, err := w.Write(status); err != nil {
|
|
http.Error(w, "Internal Server Error: failed to write data to client", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to write data to client: %s\n", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func routerProtocolsHandler(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
all := r.URL.Query().Get("all") != ""
|
|
|
|
if code == "" {
|
|
http.Error(w, "Bad Request: no router selected", http.StatusBadRequest)
|
|
return
|
|
}
|
|
router, err := getRouterByCode(code)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed to lookup router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to lookup router: %s\n", err)
|
|
return
|
|
}
|
|
protocols, err := router.getProtocols(all)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed query router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to query router: %s\n", err)
|
|
return
|
|
}
|
|
|
|
if _, err := w.Write(protocols); err != nil {
|
|
http.Error(w, "Internal Server Error: failed to write data to client", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to write data to client: %s\n", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func routerExecuteHandler(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
tool := r.URL.Query().Get("tool")
|
|
target := r.URL.Query().Get("target")
|
|
|
|
if code == "" {
|
|
http.Error(w, "Bad Request: no router selected", http.StatusBadRequest)
|
|
return
|
|
}
|
|
router, err := getRouterByCode(code)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed to lookup router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to lookup router: %s\n", err)
|
|
return
|
|
}
|
|
err = isValidTarget(target)
|
|
if err != nil {
|
|
http.Error(w, "Bad Request: invalid target", http.StatusBadRequest)
|
|
return
|
|
}
|
|
path := router.formatPath(fmt.Sprintf("/exec?tool=%s&target=%s", url.QueryEscape(tool), url.QueryEscape(target)))
|
|
|
|
res, err := http.Get(path)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error: failed to contact router", http.StatusInternalServerError)
|
|
log.Printf("ERROR: failed to contact router: %s\n", err)
|
|
return
|
|
}
|
|
scanner := bufio.NewScanner(res.Body)
|
|
scanner.Split(bufio.ScanLines)
|
|
for scanner.Scan() {
|
|
fmt.Fprintf(w, "%s\n", scanner.Bytes())
|
|
if f, ok := w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Main
|
|
*/
|
|
|
|
func main() {
|
|
config := loadConfig()
|
|
formattedConfig, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
log.Fatalf("Error formatting config: %v", err)
|
|
}
|
|
log.Printf("Loaded configuration: %s\n", formattedConfig)
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/health", middlewareChain(healthHandler))
|
|
mux.HandleFunc("/api/routers", middlewareChain(routersHandler))
|
|
mux.HandleFunc("POST /api/{code}/status", middlewareChain(routerStatusHandler))
|
|
mux.HandleFunc("POST /api/{code}/prefixes", middlewareChain(routerPrefixHandler))
|
|
mux.HandleFunc("POST /api/{code}/protocols", middlewareChain(routerProtocolsHandler))
|
|
mux.HandleFunc("POST /api/{code}/execute", middlewareChain(routerExecuteHandler))
|
|
|
|
log.Printf("starting listner on %s:%d\n", config.LG.Host, config.LG.Port)
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", config.LG.Host, config.LG.Port), mux))
|
|
}
|