Added support for custom ports

Signed-off-by: arzumify <jliwin98@danwin1210.de>
This commit is contained in:
Tracker-Friendly 2024-11-14 19:48:39 +00:00
parent d04a40f655
commit 5145c65d04
3 changed files with 179 additions and 153 deletions

View File

@ -30,4 +30,4 @@ clear
fancy "\033[1;105m" "Building Fulgens..."
go build --ldflags "-s -w" -o "$path/fulgens" || exit 1
clear
fancy "\033[1;102m" "Fulgensfas has been built successfully!"
fancy "\033[1;102m" "Fulgens has been built successfully!"

View File

@ -4,10 +4,6 @@
global: {
# IP defines the IP address to bind to.
ip: "0.0.0.0",
# httpPort defines the port to bind to for HTTP.
httpPort: "8080",
# httpsPort defines the port to bind to for HTTPS (TLS).
httpsPort: "8443",
# serviceDirectory defines the directory to look for services in.
serviceDirectory: "./services",
# resourceDirectory defines the directory to look for resources in.
@ -19,13 +15,6 @@ global: {
# level defines the compression level to use, possible values are 1-9 for gzip, 0-11 for brotli and 1-22 for zstd.
level: 5
},
# https defines the HTTPS settings on a global level - per-route settings override these. It is optional.
https: {
# certificate defines the path to the certificate file (must be a wildcard in order to support multiple subdomains).
certificate: "./certs/localhost.crt",
# key defines the path to the key file (must be a wildcard in order to support multiple subdomains).
key: "./certs/localhost.key"
},
# logging defines the logging settings.
logging: {
# enabled defines whether logging is enabled.
@ -41,6 +30,23 @@ global: {
path: "./databases",
# connectionString defines the connection string to use for the database (postgres only).
connectionString: "postgres://user:password@localhost:5432/database"
},
# stealth enables stealth mode, which makes the server look like some preset http servers.
# stealth mode overrides all proxy preservations and headers.
stealth: {
# enabled defines whether stealth mode is enabled.
enabled: true,
# server defines the server to pretend to be, possible values are "nginx" or "net/http".
server: "nginx",
# php defines if the server should pretend to be running PHP. This should only be used on nginx.
php: {
# enabled defines whether PHP spoofing is enabled.
enabled: true,
# version defines the version of PHP to pretend to be.
version: "8.2.25"
},
# aspnet defines if the server should pretend to be running ASP.NET. This should only be used on nginx.
aspNet: true
}
}
@ -49,6 +55,8 @@ routes: [
{
# none is a special subdomain that matches all requests without a subdomain (Host header).
subdomain: "none",
# port defines the port to use for this route. They do not have to be unique.
port: "8080",
# services defines the services to use for this route. Services must be defined on a per-subdomain basis.
# Each service may not be used more than once globally. The server will fail to start if this is violated.
services: ["authentication"]
@ -56,7 +64,11 @@ routes: [
{
# any subdomain value that isn't "none" will match that specific subdomain.
subdomain: "www.localhost",
# https defines the HTTPS settings for this route.
# port defines the port to use for this route. They do not have to be unique.
port: "8443",
# https defines the HTTPS settings for this route. If this block is missing, HTTPS will not be enabled for this port.
# If https is set once for any subdomain with this port, it will be enabled for all subdomains with this port.
# The connection will fail if the above condition is true, but there is not an HTTPS block for that subdomain.
https: {
# certificate defines the path to the certificate file.
certificate: "./certs/localhost.crt",
@ -66,8 +78,8 @@ routes: [
# paths defines per-path settings (NOT for services, which MUST be defined on a per-subdomain basis).
paths: [
{
# path defines the path to match. They can contain wildcards.
path: "/static/*",
# paths defines the paths to match. They can contain wildcards.
paths: ["/static", "/static/*"],
# static defines the static file serving settings for this path. This conflicts with proxy and redirect.
# static > proxy > redirect in terms of precedence.
static: {
@ -79,39 +91,43 @@ routes: [
}
},
{
# path defines the path to match. They can contain wildcards.
path: "/proxy/*",
# paths defines the paths to match. They can contain wildcards.
paths: ["/proxy", "/proxy/*"],
# proxy defines the proxy settings for this path. This conflicts with static and redirect.
# static > proxy > redirect in terms of precedence.
proxy: {
# url defines the URL to proxy requests to.
url: "http://localhost:8000",
# stripPrefix defines whether to strip the prefix from the path before proxying.
stripPrefix: true
stripPrefix: true,
headers: {
# forbid defines the headers to forbid from being sent to the proxied server.
forbid: ["Authorization"],
# preserve defines the headers to preserve when sending to the client.
preserve: [X-Powered-By", "Server"]
# host defines whether the host / :authority header should be sent to the proxied server.
host: true,
forbid: [ "User-Agent" ],
# preserveServer defines whether to preserve the server header from the proxied server.
preserveServer: true,
# preserveAltSvc defines whether to preserve the Alt-Svc header from the proxied server.
preserveAltSvc: true,
# preserveXPoweredBy defines whether to preserve the X-Powered-By header from the proxied server.
preserveXPoweredBy: true,
# passHost defines whether the host / :authority header should be sent to the proxied server.
passHost: true,
# xForward defines whether to send the X-Forwarded-For and X-Forwarded-Proto headers.
xForward: true
xForward: false
}
},
},
{
# path defines the path to match. They can contain wildcards.
path: "/redirect/*",
# paths defines the paths to match. They can contain wildcards.
paths: ["/redirect", "/redirect/*"],
# redirect defines the redirect settings for this path. This conflicts with proxy and static.
# static > proxy > redirect in terms of precedence.
redirect: {
# url defines the URL to redirect to.
url: "https://www.google.com",
url: "https://www.ailur.dev",
# permanent defines whether the redirect is permanent (301) or temporary (302).
permanent: true
}
}
}
]
}
]

216
main.go
View File

@ -1,7 +1,10 @@
package main
import (
"fmt"
library "git.ailur.dev/ailur/fg-library/v2"
"os/signal"
"syscall"
"errors"
"io"
@ -40,18 +43,9 @@ import (
type Config struct {
Global struct {
IP string `yaml:"ip" validate:"required,ip_addr"`
HTTPPort string `yaml:"httpPort" validate:"required"`
HTTPSPort string `yaml:"httpsPort" validate:"required"`
ServiceDirectory string `yaml:"serviceDirectory" validate:"required"`
ResourceDirectory string `yaml:"resourceDirectory" validate:"required"`
Compression struct {
Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"`
Level float64 `yaml:"level" validate:"omitempty,min=1,max=22"`
} `yaml:"compression"`
HTTPS struct {
CertificatePath string `yaml:"certificate" validate:"required"`
KeyPath string `yaml:"key" validate:"required"`
} `yaml:"https"`
Compression CompressionSettings `yaml:"compression"`
Logging struct {
Enabled bool `yaml:"enabled"`
File string `yaml:"file" validate:"required_if=Enabled true"`
@ -72,6 +66,7 @@ type Config struct {
}
} `yaml:"global" validate:"required"`
Routes []struct {
Port string `yaml:"port" validate:"required"`
Subdomain string `yaml:"subdomain" validate:"required"`
Services []string `yaml:"services"`
Paths []struct {
@ -94,10 +89,7 @@ type Config struct {
CertificatePath string `yaml:"certificate" validate:"required"`
KeyPath string `yaml:"key" validate:"required"`
} `yaml:"https"`
Compression struct {
Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"`
Level float64 `yaml:"level" validate:"omitempty,min=1,max=22"`
} `yaml:"compression"`
Compression CompressionSettings `yaml:"compression"`
} `yaml:"routes"`
Services map[string]interface{} `yaml:"services"`
}
@ -119,22 +111,75 @@ type Service struct {
}
type CompressionSettings struct {
Level int
Algorithm string
Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"`
Level float64 `yaml:"level" validate:"omitempty,min=1,max=22"`
}
func checkCompressionAlgorithm(algorithm string, handler http.Handler, request *http.Request) http.Handler {
var compressionLevel int
compressionSettings, ok := compression[request.Host]
type RouterAndCompression struct {
Router *chi.Mux
Compression CompressionSettings
}
type PortRouter struct {
https struct {
enabled bool
httpSettings map[string]*tls.Certificate
}
routers map[string]RouterAndCompression
}
func NewPortRouter() *PortRouter {
return &PortRouter{
routers: make(map[string]RouterAndCompression),
https: struct {
enabled bool
httpSettings map[string]*tls.Certificate
}{enabled: false, httpSettings: make(map[string]*tls.Certificate)},
}
}
func (pr *PortRouter) Register(router *chi.Mux, compression CompressionSettings, subdomain string, certificate ...*tls.Certificate) {
fmt.Println(subdomain)
pr.routers[subdomain] = RouterAndCompression{Router: router, Compression: compression}
fmt.Println(pr.routers)
if len(certificate) > 0 {
pr.https.enabled = true
pr.https.httpSettings[subdomain] = certificate[0]
}
}
func (pr *PortRouter) Router(w http.ResponseWriter, r *http.Request) {
host := strings.Split(r.Host, ":")[0]
router, ok := pr.routers[host]
if !ok {
compressionLevel = int(config.Global.Compression.Level)
} else {
compressionLevel = compressionSettings.Level
fmt.Println(pr.routers)
router, ok = pr.routers["none"]
}
switch algorithm {
if router.Compression.Algorithm != "none" {
compressRouter(router.Compression, router.Router).ServeHTTP(w, r)
} else {
router.Router.ServeHTTP(w, r)
}
}
func (pr *PortRouter) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, ok := pr.https.httpSettings[hello.ServerName]
if !ok {
return nil, errors.New("certificate not found")
} else {
return cert, nil
}
}
func (pr *PortRouter) HTTPSEnabled() bool {
return pr.https.enabled
}
func compressRouter(settings CompressionSettings, handler http.Handler) http.Handler {
switch settings.Algorithm {
case "gzip":
encoder, err := gzip.New(gzip.Options{Level: compressionLevel})
encoder, err := gzip.New(gzip.Options{Level: int(settings.Level)})
if err != nil {
slog.Error("Error creating gzip encoder: " + err.Error())
return handler
@ -146,7 +191,7 @@ func checkCompressionAlgorithm(algorithm string, handler http.Handler, request *
}
return gzipHandler(handler)
case "brotli":
encoder, err := brotli.New(brotli.Options{Quality: compressionLevel})
encoder, err := brotli.New(brotli.Options{Quality: int(settings.Level)})
if err != nil {
slog.Error("Error creating brotli encoder: " + err.Error())
return handler
@ -158,7 +203,7 @@ func checkCompressionAlgorithm(algorithm string, handler http.Handler, request *
}
return brotliHandler(handler)
case "zstd":
encoder, err := zstd.New(kpzstd.WithEncoderLevel(kpzstd.EncoderLevelFromZstd(compressionLevel)))
encoder, err := zstd.New(kpzstd.WithEncoderLevel(kpzstd.EncoderLevelFromZstd(int(settings.Level))))
if err != nil {
slog.Error("Error creating zstd encoder: " + err.Error())
return handler
@ -470,35 +515,13 @@ func serverError(w http.ResponseWriter, status int) {
}
}
func hostRouter(w http.ResponseWriter, r *http.Request) {
host := strings.Split(r.Host, ":")[0]
router, ok := subdomains[host]
if !ok {
router, ok = subdomains["none"]
if !ok {
serverError(w, 404)
slog.Error("No subdomain found for " + host)
}
}
compressionSettings, ok := compression[host]
if !ok {
checkCompressionAlgorithm(config.Global.Compression.Algorithm, router, r).ServeHTTP(w, r)
} else {
checkCompressionAlgorithm(compressionSettings.Algorithm, router, r).ServeHTTP(w, r)
}
}
var (
validate *validator.Validate
lock sync.RWMutex
config Config
registeredServices = make(map[string]Service)
activeServices = make(map[uuid.UUID]Service)
certificates = make(map[string]*tls.Certificate)
compression = make(map[string]CompressionSettings)
subdomains = make(map[string]*chi.Mux)
portRouters = make(map[string]*PortRouter)
)
func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, error) {
@ -510,19 +533,6 @@ func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, erro
}
}
func getTLSCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, ok := certificates[hello.ServerName]
if !ok {
if config.Global.HTTPS.CertificatePath == "" || config.Global.HTTPS.KeyPath == "" {
return nil, errors.New("no certificate found")
} else {
return certificates["none"], nil
}
} else {
return cert, nil
}
}
func svInit(message library.InterServiceMessage) {
// Service database initialization message
// Check if the service has the necessary permissions
@ -883,11 +893,11 @@ func parseConfig(path string) Config {
func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
for _, route := range config.Routes {
if route.Compression.Level != 0 {
compression[route.Subdomain] = CompressionSettings{
Level: int(route.Compression.Level),
Algorithm: route.Compression.Algorithm,
}
var compressionSettings CompressionSettings
if route.Compression != (CompressionSettings{}) {
compressionSettings = route.Compression
} else {
compressionSettings = config.Global.Compression
}
// Create the subdomain router
@ -896,9 +906,22 @@ func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
serverError(w, 404)
})
subdomains[route.Subdomain] = subdomainRouter
subdomains[route.Subdomain].Use(logger)
subdomains[route.Subdomain].Use(serverChanger)
_, ok := portRouters[route.Port]
if !ok {
portRouters[route.Port] = NewPortRouter()
}
// Check if HTTPS is enabled
if route.HTTPS.KeyPath != "" && route.HTTPS.CertificatePath != "" {
certificate, err := loadTLSCertificate(route.HTTPS.CertificatePath, route.HTTPS.KeyPath)
if err != nil {
slog.Error("Error loading TLS certificate: " + err.Error())
os.Exit(1)
}
portRouters[route.Port].Register(subdomainRouter, compressionSettings, route.Subdomain, certificate)
} else {
portRouters[route.Port].Register(subdomainRouter, compressionSettings, route.Subdomain)
}
// Check the services
if route.Services != nil {
@ -953,15 +976,6 @@ func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
}
}
}
// Add the TLS certificate
if route.HTTPS.CertificatePath != "" && route.HTTPS.KeyPath != "" {
certificate, err := loadTLSCertificate(route.HTTPS.CertificatePath, route.HTTPS.KeyPath)
if err != nil {
slog.Error("Error loading TLS certificate: " + err.Error() + ", TLS will not be available for subdomain " + route.Subdomain)
}
certificates[route.Subdomain] = certificate
}
}
}
@ -1075,16 +1089,6 @@ func main() {
}
}
// Check if the root TLS certificate exists
if config.Global.HTTPS.CertificatePath != "" && config.Global.HTTPS.KeyPath != "" {
certificate, err := loadTLSCertificate(config.Global.HTTPS.CertificatePath, config.Global.HTTPS.KeyPath)
if err != nil {
slog.Error("Error loading TLS certificate: " + err.Error() + ", TLS will not be available unless specified in the subdomains")
}
certificates["none"] = certificate
}
// Walk through the service directory and load the plugins
err := registerServices()
if err != nil {
@ -1105,31 +1109,37 @@ func main() {
// Iterate through the subdomains and create the routers as well as the compression levels and service maps
iterateThroughSubdomains(globalOutbox)
// Start the server
slog.Info("Starting server on " + config.Global.IP + " with ports " + config.Global.HTTPPort + " and " + config.Global.HTTPSPort)
// Start the servers
for port, router := range portRouters {
slog.Info("Starting server on port " + port)
if !router.HTTPSEnabled() {
go func() {
// Start the HTTP server
err = http.ListenAndServe(config.Global.IP+":"+port, logger(serverChanger(http.HandlerFunc(router.Router))))
slog.Error("Error starting server: " + err.Error())
os.Exit(1)
}()
} else {
// Create the TLS server
server := &http.Server{
Addr: config.Global.IP + ":" + config.Global.HTTPSPort,
Handler: http.HandlerFunc(hostRouter),
Addr: config.Global.IP + ":" + port,
Handler: logger(serverChanger(http.HandlerFunc(router.Router))),
TLSConfig: &tls.Config{
GetCertificate: getTLSCertificate,
GetCertificate: router.GetCertificate,
},
}
go func() {
// Start the TLS server
err = server.ListenAndServeTLS("", "")
slog.Error("Error starting HTTPS server: " + err.Error())
os.Exit(1)
}()
}
}
// Start the HTTP server
err = http.ListenAndServe(config.Global.IP+":"+config.Global.HTTPPort, http.HandlerFunc(hostRouter))
if err != nil {
slog.Error("Error starting server: " + err.Error())
} else {
// This should never happen
slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.")
}
os.Exit(1)
// Wait for a signal to stop the server
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
<-signalChannel
}