logging and client info parsing changes, added trusted proxies

This commit is contained in:
Maximilian Wagner
2025-12-27 18:02:02 +01:00
parent 8dcf9fba11
commit ad3825c871
7 changed files with 211 additions and 32 deletions

View File

@@ -4,16 +4,24 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"io/fs"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"
"embed"
"git.noctra.dev/noctra/servtex/globals"
"github.com/fsnotify/fsnotify"
)
//go:embed default_config.json
var defaultConfig embed.FS
// Returns the current time in the timezone specified in the config file
func GetLocalTime() time.Time {
timezone, _ := time.LoadLocation(globals.AppConfig.Timezone)
@@ -49,6 +57,30 @@ func LogLine(message string, level int) {
globals.LogFile.WriteString(logline)
}
// Logs client info. Configured log level sets the verbosity.
//
// Bad Request if err != nil
func VerifyLogRequest(request *http.Request) (err error) {
client, err := ProcessClientInfo(request)
if err != nil { return errors.New("Request Verification Failed") }
if client.Proxied && globals.AppConfig.LogLevelNumeric > 1 {
LogLine(fmt.Sprintf(
"Request: %s via %s - %s %s",
client.ClientIP,
client.Proxy,
client.RequestType,
client.RequestPath,
),
4,
)
} else {
LogLine(fmt.Sprintf("Request: %s - %s %s", client.ClientIP, client.RequestType, client.RequestPath), 4)
}
return nil
}
// Helper for configReader() that does the actual reading
//
// Also validates the populated fields
@@ -56,11 +88,11 @@ func configReaderParse(filePath string, configOptionStorage *globals.Config) err
jsonData, err := os.ReadFile(filePath)
if err == nil {
if err = json.Unmarshal(jsonData, &configOptionStorage); err != nil {
LogLine("Configuration file is invaldi JSON", 5)
LogLine("Configuration file is invalid JSON", 5)
return errors.New("Config file could not be read")
}
} else {
LogLine(fmt.Sprintf("Config at %s does not exist", filePath), 1)
LogLine("Configuration file does not exist", 1)
return errors.New("Config file does not exist")
}
@@ -77,7 +109,7 @@ func configReaderParse(filePath string, configOptionStorage *globals.Config) err
configOptionStorage.LogLevelNumeric = 3
LogLine("Defaulting to log level warning", 3)
}
return configValidator(*configOptionStorage)
return nil
}
// tbd what exactly happens here
@@ -85,6 +117,14 @@ func configValidator(config globals.Config) error {
return nil
}
func configPopulator(config globals.Config) error {
return nil
}
// Creates the configuration file populated with defaults
func createConfigWithDefaults() {
}
// Reads config file and stores the options in configOptionStorage
//
// Tries the following paths
@@ -108,9 +148,15 @@ func ConfigReader(configFileName string, configOptionStorage *globals.Config) er
path := filepath.Join("~", ".config", "servtex", configFileName)
err = configReaderParse(path, configOptionStorage)
if err == nil { return nil }
if err != nil { return err }
return err
err = configPopulator(*configOptionStorage)
if err != nil { return err }
err = configValidator(*configOptionStorage)
if err != nil { return err }
return nil
}
// Contents copied from https://github.com/fsnotify/fsnotify/blob/main/cmd/fsnotify/watch.go
@@ -240,3 +286,88 @@ func LatexCompile() error {
return nil
}
// Returns intersection of two slices
func sliceIntersection[T comparable](left []T, right []T) (intersection []T) {
for _, leftItem := range left {
for _, rightItem := range right {
if leftItem == rightItem {
intersection = append(intersection, leftItem)
}
}
}
return intersection
}
// Checks whether the proxy is trusted. Returns trusted status and the proxy.
//
// If X-Forwarded-For chain is passed, only the last in chain will be considered
func isTrustedProxy(proxy string, trustedProxies []string) (trusted bool, usedProxy string) {
// removes len()>0 checks
if proxy == "" { return false, "" }
// untrusted unless proven otherwise
trusted = false
// get (last) used proxy (in chain)
if slices.Contains(trustedProxies, proxy) {
usedProxy = proxy
} else {
proxies := strings.Split(proxy, ",")[1:]
usedProxy = proxies[len(proxies)-1]
}
// check if proxy is trusted
if slices.Contains(trustedProxies, usedProxy) { return true, usedProxy }
// resolve trustedProxies in case a DNS name was configured
for _, trustedProxyDNS := range trustedProxies {
// get IPs of this dns name
trustedProxyIPs, err := net.LookupAddr(trustedProxyDNS)
if err != nil { continue }
// set usedProxy to DNS name if proxy is trusted
if slices.Contains(trustedProxyIPs, usedProxy) {
trusted = true
usedProxy = trustedProxyDNS
return trusted, usedProxy
}
}
return trusted, usedProxy
}
// Returns client info of the request. Error indicates untrusted Proxy or unavailable client IP.
func ProcessClientInfo(request *http.Request) (client globals.ClientInfo, err error) {
client.ClientIP, _, err = net.SplitHostPort(request.RemoteAddr)
client.RequestType = request.Method
client.RequestPath = request.URL.Path
client.Proxy = ""
client.Proxied = false
client.ProxyTrusted = false
trustedProxies := globals.AppConfig.TrustedProxies
headerXRP := request.Header.Get("X-Real-IP")
if headerXRP != "" {
client.Proxied = true
client.ProxyTrusted, client.Proxy = isTrustedProxy(headerXRP, trustedProxies)
if !client.ProxyTrusted {
LogLine(fmt.Sprintf("Request sent via untrusted Proxy %s", client.Proxy), 4)
err = errors.New("Untrusted Proxy")
}
return client, err
}
headerXFF := request.Header.Get("X-Forwarded-For")
if headerXFF != "" {
client.Proxied = true
client.ProxyTrusted, client.Proxy = isTrustedProxy(headerXRP, trustedProxies)
if !client.ProxyTrusted {
LogLine(fmt.Sprintf("Request sent via untrusted Proxy %s", client.Proxy), 4)
err = errors.New("Untrusted Proxy")
}
return client, err
}
return client, err
}

View File

@@ -0,0 +1,17 @@
{
"timezone": "Europe/Berlin"
"logFilePath": "./servtex.log",
"logLevel": "warning",
"latexEngine": "lualatex",
"latexSourceFilePath": "./latex/Example.tex",
"latexOutputPath": "./latex/output",
"webserverDomain": "localhost",
"webserverPort": "8080",
"webserverSecure": true,
"webserverPortSecure": "8443",
"certificatePath": "./tls/testing.crt",
"certificateKeyPath": "./tls/testing.key",
"trustedProxies": []
}