Skip to content

Commit

Permalink
Breaking change: remove GetReverseProxyHandler, DefaultTLSConfig(), S…
Browse files Browse the repository at this point in the history
…tartPrometheusClient(), DefaultReverseProxyHTTPHandler(), DefaultProxyServer().

Refactor CLI flags and Prometheus metrics. Add CLI flag -duration-metric-buckets, -preserve-host.
  • Loading branch information
wi1dcard committed Mar 28, 2024
1 parent b6a145d commit c2473c8
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 162 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Fingerprints are in the HTTP request headers:
}
```

For the complete CLI options, see `fingerproxy --help`.
Fingerproxy supports Kubernetes liveness probe and Prometheus metrics. For the complete CLI options, see `fingerproxy --help`.

## Implement Your Fingerprinting Algorithm

Expand Down
24 changes: 24 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fingerproxy

import (
"os"
"strings"
)

func envWithDefault(key string, defaultVal string) string {
if envVal, ok := os.LookupEnv(key); ok {
return envVal
}
return defaultVal
}

func envWithDefaultBool(key string, defaultVal bool) bool {
if envVal, ok := os.LookupEnv(key); ok {
if strings.ToLower(envVal) == "true" {
return true
} else if strings.ToLower(envVal) == "false" {
return false
}
}
return defaultVal
}
178 changes: 49 additions & 129 deletions fingerproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -51,16 +48,9 @@ var (

PrometheusRegistry = prometheus.NewRegistry()

GetHeaderInjectors = DefaultHeaderInjectors
GetReverseProxyHTTPHandler = DefaultReverseProxyHTTPHandler
GetHeaderInjectors = DefaultHeaderInjectors
)

func InitFingerprint(verboseLogs bool) {
fingerprint.VerboseLogs = verboseLogs
fingerprint.Logger = FingerprintLog
fingerprint.MetricsRegistry = PrometheusRegistry
}

func DefaultHeaderInjectors() []reverseproxy.HeaderInjector {
return []reverseproxy.HeaderInjector{
fingerprint.NewFingerprintHeaderInjector("X-JA3-Fingerprint", fingerprint.JA3Fingerprint),
Expand All @@ -69,29 +59,6 @@ func DefaultHeaderInjectors() []reverseproxy.HeaderInjector {
}
}

func DefaultTLSConfig(certFile string, keyFile string) (*tls.Config, error) {
conf := &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
}

if tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile); err != nil {
return nil, err
} else {
conf.Certificates = []tls.Certificate{tlsCert}
}

return conf, nil
}

func StartPrometheusClient(listenAddr string) {
PrometheusLog.Printf("server listening on %s", listenAddr)
go http.ListenAndServe(listenAddr, promhttp.HandlerFor(PrometheusRegistry, promhttp.HandlerOpts{
ErrorLog: PrometheusLog,
}))
}

func proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
ReverseProxyLog.Printf("proxy %s error (from %s): %v", req.URL.String(), req.RemoteAddr, err)

Expand All @@ -103,8 +70,8 @@ func proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
}
}

func DefaultReverseProxyHTTPHandler(forwardTo *url.URL) *reverseproxy.HTTPHandler {
return reverseproxy.NewHTTPHandler(
func defaultReverseProxyHTTPHandler(forwardTo *url.URL, headerInjectors []reverseproxy.HeaderInjector) http.Handler {
handler := reverseproxy.NewHTTPHandler(
forwardTo,
&httputil.ReverseProxy{
ErrorLog: ReverseProxyLog,
Expand All @@ -113,15 +80,23 @@ func DefaultReverseProxyHTTPHandler(forwardTo *url.URL) *reverseproxy.HTTPHandle
// TODO: customize transport
Transport: http.DefaultTransport.(*http.Transport).Clone(),
},
GetHeaderInjectors(),
headerInjectors,
)

handler.PreserveHost = *flagPreserveHost

if *flagEnableKubernetesProbe {
handler.IsProbeRequest = reverseproxy.IsKubernetesProbeRequest
}

return handler
}

func DefaultProxyServer(handler http.Handler, tlsConfig *tls.Config, verboseLogs bool) *proxyserver.Server {
func defaultProxyServer(handler http.Handler, tlsConfig *tls.Config) *proxyserver.Server {
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
svr := proxyserver.NewServer(ctx, handler, tlsConfig)

svr.VerboseLogs = verboseLogs
svr.VerboseLogs = *flagVerboseLogs
svr.ErrorLog = ProxyServerLog
svr.HTTPServer.ErrorLog = HTTPServerLog

Expand All @@ -135,103 +110,48 @@ func DefaultProxyServer(handler http.Handler, tlsConfig *tls.Config, verboseLogs
return svr
}

func Run() {
flagListenAddr := flag.String(
"listen-addr",
envWithDefault("LISTEN_ADDR", ":443"),
"Listening address, equivalent to $LISTEN_ADDR",
)

flagForwardURL := flag.String(
"forward-url",
envWithDefault("FORWARD_URL", "http://localhost:80"),
"Backend URL that the requests will be forwarded to, equivalent to $FORWARD_URL",
)

flagCertFilename := flag.String(
"cert-filename",
envWithDefault("CERT_FILENAME", "tls.crt"),
"TLS certificate filename, equivalent to $CERT_FILENAME",
)

flagKeyFilename := flag.String(
"certkey-filename",
envWithDefault("CERTKEY_FILENAME", "tls.key"),
"TLS certificate key file name, equivalent to $CERTKEY_FILENAME",
)

flagMetricsListenAddr := flag.String(
"metrics-listen-addr",
envWithDefault("METRICS_LISTEN_ADDR", ":9035"),
"Listening address of Prometheus metrics, equivalent to $METRICS_LISTEN_ADDR",
)

flagEnableKubernetesProbe := flag.Bool(
"enable-kubernetes-probe",
envWithDefaultBool("ENABLE_KUBERNETES_PROBE", true),
"Enable kubernetes liveness/readiness probe support, equivalent to $ENABLE_KUBERNETES_PROBE",
)
func initFingerprint() {
fingerprint.Logger = FingerprintLog
fingerprint.VerboseLogs = *flagVerboseLogs
fingerprint.RegisterDurationMetric(PrometheusRegistry, parseDurationMetricBuckets(), "")
}

flagVerboseLogs := flag.Bool(
"verbose",
envWithDefaultBool("VERBOSE", false),
"Enable verbose logs, equivalent to $VERBOSE",
func Run() {
// CLI
initFlags()
parseFlags()

// fingerprint package
initFingerprint()

// main TLS server
server := defaultProxyServer(
defaultReverseProxyHTTPHandler(
parseForwardURL(),
GetHeaderInjectors(),
),
&tls.Config{
NextProtos: []string{"h2", "http/1.1"},
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{parseTLSCerts()},
},
)

flagVersion := flag.Bool("version", false, "Print version and exit")
flag.Parse()

if *flagVersion {
fmt.Fprintln(os.Stderr, "Fingerproxy - https://github.com/wi1dcard/fingerproxy")
fmt.Fprintf(os.Stderr, "Version: %s (%s)\n", BuildTag, BuildCommit)
os.Exit(0)
}

forwardTo, err := url.Parse(*flagForwardURL)
if err != nil {
DefaultLog.Fatal(err)
}

tlsConfig, err := DefaultTLSConfig(*flagCertFilename, *flagKeyFilename)
if err != nil {
DefaultLog.Fatal(err)
}

InitFingerprint(*flagVerboseLogs)

handler := GetReverseProxyHTTPHandler(forwardTo)
if *flagEnableKubernetesProbe {
handler.IsProbeRequest = reverseproxy.IsKubernetesProbeRequest
}

server := DefaultProxyServer(
handler,
tlsConfig,
*flagVerboseLogs,
// metrics server
PrometheusLog.Printf("server listening on %s", *flagMetricsListenAddr)
go http.ListenAndServe(
*flagMetricsListenAddr,
promhttp.HandlerFor(PrometheusRegistry, promhttp.HandlerOpts{
ErrorLog: PrometheusLog,
}),
)

StartPrometheusClient(*flagMetricsListenAddr)
// debug server if binary build with `debug` tag
debug.StartDebugServer()

// start the main TLS server
DefaultLog.Printf("server listening on %s", *flagListenAddr)
err = server.ListenAndServe(*flagListenAddr)
err := server.ListenAndServe(*flagListenAddr)
DefaultLog.Print(err)
}

func envWithDefault(key string, defaultVal string) string {
if envVal, ok := os.LookupEnv(key); ok {
return envVal
}
return defaultVal
}

func envWithDefaultBool(key string, defaultVal bool) bool {
if envVal, ok := os.LookupEnv(key); ok {
if strings.ToLower(envVal) == "true" {
return true
} else if strings.ToLower(envVal) == "false" {
return false
}
}
return defaultVal
}
124 changes: 124 additions & 0 deletions flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package fingerproxy

import (
"crypto/tls"
"flag"
"fmt"
"net/url"
"os"
"strconv"
"strings"
)

var (
flagListenAddr *string
flagForwardURL *string
flagCertFilename *string
flagKeyFilename *string
flagMetricsListenAddr *string
flagDurationMetricBuckets *string
flagPreserveHost *bool
flagEnableKubernetesProbe *bool
flagVerboseLogs *bool
flagVersion *bool
)

func initFlags() {
flagListenAddr = flag.String(
"listen-addr",
envWithDefault("LISTEN_ADDR", ":443"),
"Listening address, equivalent to $LISTEN_ADDR",
)

flagForwardURL = flag.String(
"forward-url",
envWithDefault("FORWARD_URL", "http://localhost:80"),
"Backend URL that the requests will be forwarded to, equivalent to $FORWARD_URL",
)

flagCertFilename = flag.String(
"cert-filename",
envWithDefault("CERT_FILENAME", "tls.crt"),
"TLS certificate filename, equivalent to $CERT_FILENAME",
)

flagKeyFilename = flag.String(
"certkey-filename",
envWithDefault("CERTKEY_FILENAME", "tls.key"),
"TLS certificate key file name, equivalent to $CERTKEY_FILENAME",
)

flagMetricsListenAddr = flag.String(
"metrics-listen-addr",
envWithDefault("METRICS_LISTEN_ADDR", ":9035"),
"Listening address of Prometheus metrics, equivalent to $METRICS_LISTEN_ADDR",
)

flagDurationMetricBuckets = flag.String(
"duration-metric-buckets",
envWithDefault("DURATION_METRIC_BUCKETS", ".00001, .00002, .00005, .0001, .0002, .0005, .001, .005, .01"),
"The histogram buckets of duration metric, equivalent to $DURATION_METRIC_BUCKETS",
)

flagPreserveHost = flag.Bool(
"preserve-host",
envWithDefaultBool("PRESERVE_HOST", false),
"Forward HTTP Host header from incoming requests to the backend, equivalent to $PRESERVE_HOST",
)

flagEnableKubernetesProbe = flag.Bool(
"enable-kubernetes-probe",
envWithDefaultBool("ENABLE_KUBERNETES_PROBE", true),
"Enable kubernetes liveness/readiness probe support, equivalent to $ENABLE_KUBERNETES_PROBE",
)

flagVerboseLogs = flag.Bool(
"verbose",
envWithDefaultBool("VERBOSE", false),
"Enable verbose logs, equivalent to $VERBOSE",
)

flagVersion = flag.Bool("version", false, "Print version and exit")
}

func parseFlags() {
flag.Parse()

if *flagVersion {
fmt.Fprintln(os.Stderr, "Fingerproxy - https://github.com/wi1dcard/fingerproxy")
fmt.Fprintf(os.Stderr, "Version: %s (%s)\n", BuildTag, BuildCommit)
os.Exit(0)
}
}

func parseForwardURL() *url.URL {
forwardURL, err := url.Parse(*flagForwardURL)
if err != nil {
DefaultLog.Fatal(err)
}

return forwardURL
}

func parseTLSCerts() tls.Certificate {
tlsCert, err := tls.LoadX509KeyPair(*flagCertFilename, *flagKeyFilename)
if err != nil {
DefaultLog.Fatal(err)
}
return tlsCert
}

func parseDurationMetricBuckets() []float64 {
bucketStrings := strings.Split(*flagDurationMetricBuckets, ",")
buckets := []float64{}

for _, bucket := range bucketStrings {
parsedBucket, err := strconv.ParseFloat(strings.Trim(bucket, " "), 64)
if err != nil {
DefaultLog.Fatalf("bad duration metric buckets: %s", err)
}
buckets = append(buckets, parsedBucket)
}

return buckets
}
Loading

0 comments on commit c2473c8

Please sign in to comment.