Added support for custom ports
Signed-off-by: arzumify <jliwin98@danwin1210.de>
This commit is contained in:
parent
d04a40f655
commit
5145c65d04
2
build.sh
2
build.sh
|
@ -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!"
|
|
@ -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
216
main.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue