go-version #1

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

Binary file not shown.

View File

@@ -1,35 +1,154 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"git.mziesel.nl/mans/lg/internal/socket"
)
type LgAgentConfig struct {
Host string `json:"host"`
Port int `json:"port"`
SocketPath string `json:"socket_path"`
SharedSecret string `json:"shared_secret"`
}
var config LgAgentConfig
func loadConfig() LgAgentConfig {
// Default configuration
config = LgAgentConfig{
Host: "localhost",
Port: 3000,
SocketPath: "/var/run/bird/bird.ctl",
SharedSecret: "", // empty = no secret
}
// Determine the config file path
configPath := "/etc/lg-agent/agent.json"
if envPath := os.Getenv("LG_AGENT_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.SharedSecret == "" {
return true
}
authHeader := r.Header.Get("Authorization")
return authHeader == fmt.Sprintf("Bearer %s", config.SharedSecret)
}
func ensureAuthenticatedMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Error(w, `{"error": "Unauthorized access"}`, http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}
}
func plainTextMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
next(w, r)
}
}
func jsonMiddleware(next 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))
}
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 birdStatusHandler(w http.ResponseWriter, r *http.Request) {
s := socket.NewBirdSocket(config.SocketPath, 4092, true)
s.Connect()
defer s.Close()
status, _, err := s.Send("show status")
if err != nil {
http.Error(w, "Internal Server Error: failed to query router status", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte("status: " + string(status)))
if err != nil {
log.Println(err)
}
}
func birdCommandHandler(w http.ResponseWriter, r *http.Request) {
command := r.URL.Query().Get("c")
if command == "" {
http.Error(w, "Bad Request: command parameter is required", http.StatusBadRequest)
return
}
s := socket.NewBirdSocket(config.SocketPath, 4092, true)
s.Connect()
defer s.Close()
output, _, err := s.Send(command)
if err != nil {
http.Error(w, "Internal Server Error: failed to query bird", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte(string(output)))
if err != nil {
log.Println(err)
}
}
func main() {
bc := socket.NewBirdSocket("/home/mans/run/bird.ctl", 1024)
config := loadConfig()
formattedConfig, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("Error formatting config: %v", err)
}
log.Printf("Loaded configuration: %s\n", formattedConfig)
err := bc.Connect()
if err != nil {
log.Fatal(err)
}
defer bc.Close()
mux := http.NewServeMux()
resp, _, err := bc.Send("show status")
if err != nil {
log.Fatal(err)
}
fmt.Print(string(resp))
resp, _, err = bc.Send("restrict")
if err != nil {
log.Fatal(err)
}
fmt.Print(string(resp))
resp, _, err = bc.Send("show protocols")
if err != nil {
log.Fatal(err)
}
mux.HandleFunc("/health", apiMiddlewareChain(healthHandler))
mux.HandleFunc("/bird/status", apiMiddlewareChain(birdStatusHandler))
mux.HandleFunc("/bird/command", apiMiddlewareChain(birdCommandHandler))
fmt.Print(string(resp))
log.Printf("starting listner on %s:%d\n", config.Host, config.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", config.Host, config.Port), mux))
}

6
config/agent.json Normal file
View File

@@ -0,0 +1,6 @@
{
"host": "127.0.0.1",
"port": 4242,
"socket_path": "/var/run/bird/bird.ctl",
"shared_secret": ""
}

View File

@@ -1,4 +0,0 @@
host = "127.0.0.1"
port = 4242
socket_path = "/var/run/bird/bird.ctl"
shared_secret = ""

31
config/backend.json Normal file
View File

@@ -0,0 +1,31 @@
{
"lg": {
"name": "Mans's looking glass"
},
"rate_limit": {
"requests": 20,
"time_window": 300
},
"routers": {
"nur01": {
"code": "nur01",
"description": "BIRD2 router in Wierden",
"ipv4": "127.0.0.1",
"ipv6": "fe80::1",
"agent": {
"host": "127.0.0.1",
"port": "4242"
}
},
"wie01": {
"code": "wie01",
"description": "BIRD2 router in Nuremberg",
"ipv4": "127.0.0.1",
"ipv6": "fe80::1",
"agent": {
"host": "127.0.0.1",
"port": "4243"
}
}
}
}

View File

@@ -1,28 +0,0 @@
[lg]
name = "Mans's looking glass"
[rate_limit]
requests = 20
time_window = 300
[routers]
[routers.nur01]
code = "nur01"
description = "BIRD2 router in Wierden"
ipv4 = "127.0.0.1"
ipv6 = "fe80::1"
[routers.nur01.agent]
host = "127.0.0.1"
port = "4242"
[routers.wie01]
code = "wie01"
description = "BIRD2 router in Nuremberg"
ipv4 = "127.0.0.1"
ipv6 = "fe80::1"
[routers.wie01.agent]
host = "127.0.0.1"
port = "4243"

View File

@@ -1,12 +0,0 @@
package bird
import (
"bufio"
"errors"
)
func ParseRawOutput(input string) (string, error) {
reader := bufio.NewReader(input)
return "", errors.New("asdf")
}

View File

@@ -1,6 +1,6 @@
package socket
// taken from
// taken from, adapted by me
// https://github.com/StatCan/go-birdc/blob/master/socket/socket.go
import (
@@ -35,6 +35,7 @@ type BirdSocket struct {
path string
bufferSize int
conn net.Conn
restricted bool
}
// NewBirdSocket creates a new BirdSocket
@@ -42,8 +43,8 @@ type BirdSocket struct {
// `path` is the unix socket path
//
// `bufferSize` is the size of the read buffer
func NewBirdSocket(path string, bufferSize int) *BirdSocket {
return &BirdSocket{path: path, bufferSize: bufferSize}
func NewBirdSocket(path string, bufferSize int, restricted bool) *BirdSocket {
return &BirdSocket{path: path, bufferSize: bufferSize, restricted: restricted}
}
// Connect opens the unix socket connection
@@ -57,6 +58,14 @@ func (s *BirdSocket) Connect() (err error) {
buf := make([]byte, s.bufferSize)
_, err = s.conn.Read(buf[:])
_, resp_code, err := s.Send("restrict")
if err != nil {
return err
}
if string(resp_code) != "0016" { // access restricted
return errors.New("failed to restrict bird session")
}
return
}