logging and client info parsing changes, added trusted proxies
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
17
backend/default_config.json
Normal file
17
backend/default_config.json
Normal 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": []
|
||||
}
|
||||
Reference in New Issue
Block a user