A simple HTTPS ingress for Kubernetes clusters, designed to work well with Anubis.

feat(server): enable proxy protocol support with PROXY_PROTO

Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso 8d00e47b 5e237912

+48 -11
+2
cmd/hythlodaeus/main.go
··· 26 grpcKey = flag.String("grpc-key", "./var/tls.key", "the TLS key to serve grpc") 27 httpBind = flag.String("http-bind", ":80", "the host:port to bind plain HTTP") 28 httpsBind = flag.String("https-bind", ":443", "the host:port to bind secure HTTPS") 29 telemetryEnable = flag.Bool("telemetry-enable", false, "if true, enable request telemetry bundling to object storage") 30 telemetryBucket = flag.String("telemetry-bucket", "relayd-logs", "object storage bucket to dump logs to") 31 telemetryPathStyle = flag.Bool("telemetry-path-style", false, "if true, use s3 path style") ··· 60 server.WithGRPCKey(*grpcKey), 61 server.WithHTTPBind(*httpBind), 62 server.WithHTTPSBind(*httpsBind), 63 server.WithTelemetryConfig(telemetry.Config{ 64 Bucket: *telemetryBucket, 65 PathStyle: *telemetryPathStyle,
··· 26 grpcKey = flag.String("grpc-key", "./var/tls.key", "the TLS key to serve grpc") 27 httpBind = flag.String("http-bind", ":80", "the host:port to bind plain HTTP") 28 httpsBind = flag.String("https-bind", ":443", "the host:port to bind secure HTTPS") 29 + proxyProto = flag.Bool("proxy-proto", false, "if true, use haproxy proxy protocol") 30 telemetryEnable = flag.Bool("telemetry-enable", false, "if true, enable request telemetry bundling to object storage") 31 telemetryBucket = flag.String("telemetry-bucket", "relayd-logs", "object storage bucket to dump logs to") 32 telemetryPathStyle = flag.Bool("telemetry-path-style", false, "if true, use s3 path style") ··· 61 server.WithGRPCKey(*grpcKey), 62 server.WithHTTPBind(*httpBind), 63 server.WithHTTPSBind(*httpsBind), 64 + server.WithProxyProto(*proxyProto), 65 server.WithTelemetryConfig(telemetry.Config{ 66 Bucket: *telemetryBucket, 67 PathStyle: *telemetryPathStyle,
+1
go.mod
··· 12 github.com/felixge/httpsnoop v1.0.4 13 github.com/google/uuid v1.6.0 14 github.com/joho/godotenv v1.5.1 15 github.com/prometheus/client_golang v1.22.0 16 golang.org/x/net v0.40.0 17 golang.org/x/sync v0.14.0
··· 12 github.com/felixge/httpsnoop v1.0.4 13 github.com/google/uuid v1.6.0 14 github.com/joho/godotenv v1.5.1 15 + github.com/pires/go-proxyproto v0.8.1 16 github.com/prometheus/client_golang v1.22.0 17 golang.org/x/net v0.40.0 18 golang.org/x/sync v0.14.0
+2
go.sum
··· 259 github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 260 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 261 github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 262 github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 263 github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 264 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
··· 259 github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 260 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 261 github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 262 + github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= 263 + github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= 264 github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 265 github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 266 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+8
server/config.go
··· 9 grpcInsecure bool 10 httpBind string 11 httpsBind string 12 telemetryConfig telemetry.Config 13 } 14 ··· 20 grpcKey: "", 21 httpBind: ":80", 22 httpsBind: ":443", 23 } 24 } 25 ··· 59 func WithHTTPSBind(httpsBind string) Option { 60 return func(cfg *config) { 61 cfg.httpsBind = httpsBind 62 } 63 } 64
··· 9 grpcInsecure bool 10 httpBind string 11 httpsBind string 12 + proxyProto bool 13 telemetryConfig telemetry.Config 14 } 15 ··· 21 grpcKey: "", 22 httpBind: ":80", 23 httpsBind: ":443", 24 + proxyProto: false, 25 } 26 } 27 ··· 61 func WithHTTPSBind(httpsBind string) Option { 62 return func(cfg *config) { 63 cfg.httpsBind = httpsBind 64 + } 65 + } 66 + 67 + func WithProxyProto(proxyProto bool) Option { 68 + return func(cfg *config) { 69 + cfg.proxyProto = proxyProto 70 } 71 } 72
+35 -11
server/server.go
··· 13 "git.xeserv.us/Techaro/hythlodaeus/telemetry" 14 "git.xeserv.us/Techaro/hythlodaeus/watcher" 15 "github.com/exaring/ja4plus" 16 "golang.org/x/net/http2" 17 "golang.org/x/sync/errgroup" 18 "google.golang.org/grpc" ··· 93 }) 94 95 eg.Go(func() error { 96 srv := http.Server{ 97 Addr: s.cfg.httpsBind, 98 Handler: s.ja4m.Wrap(s.sink.Middleware(s)), 99 ConnState: s.ja4m.ConnStateCallback, 100 - TLSConfig: &tls.Config{ 101 - GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { 102 - return s.routingTable.Load().(*RoutingTable).GetCertificate(hello.ServerName) 103 - }, 104 - GetConfigForClient: func(chi *tls.ClientHelloInfo) (*tls.Config, error) { 105 - s.ja4m.StoreFingerprintFromClientHello(chi) 106 - return nil, nil 107 - }, 108 - }, 109 } 110 111 slog.Info("starting server", "protocol", "https", "addr", srv.Addr) 112 113 healthSrv.SetServingStatus("https", healthv1.HealthCheckResponse_SERVING) 114 115 - err := srv.ListenAndServeTLS("", "") 116 if err != nil { 117 return fmt.Errorf("error serving tls: %w", err) 118 } ··· 129 130 healthSrv.SetServingStatus("http", healthv1.HealthCheckResponse_SERVING) 131 132 - err := srv.ListenAndServe() 133 if err != nil { 134 return fmt.Errorf("error serving non-tls: %w", err) 135 }
··· 13 "git.xeserv.us/Techaro/hythlodaeus/telemetry" 14 "git.xeserv.us/Techaro/hythlodaeus/watcher" 15 "github.com/exaring/ja4plus" 16 + "github.com/pires/go-proxyproto" 17 "golang.org/x/net/http2" 18 "golang.org/x/sync/errgroup" 19 "google.golang.org/grpc" ··· 94 }) 95 96 eg.Go(func() error { 97 + tc := &tls.Config{ 98 + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { 99 + return s.routingTable.Load().(*RoutingTable).GetCertificate(hello.ServerName) 100 + }, 101 + GetConfigForClient: func(chi *tls.ClientHelloInfo) (*tls.Config, error) { 102 + s.ja4m.StoreFingerprintFromClientHello(chi) 103 + return nil, nil 104 + }, 105 + } 106 + 107 srv := http.Server{ 108 Addr: s.cfg.httpsBind, 109 Handler: s.ja4m.Wrap(s.sink.Middleware(s)), 110 ConnState: s.ja4m.ConnStateCallback, 111 + TLSConfig: tc, 112 } 113 114 slog.Info("starting server", "protocol", "https", "addr", srv.Addr) 115 116 healthSrv.SetServingStatus("https", healthv1.HealthCheckResponse_SERVING) 117 118 + lis, err := net.Listen("tcp", s.cfg.httpsBind) 119 + if err != nil { 120 + return fmt.Errorf("error listening on %s: %v", s.cfg.httpsBind, err) 121 + } 122 + defer lis.Close() 123 + 124 + if s.cfg.proxyProto { 125 + lis = &proxyproto.Listener{Listener: lis} 126 + } 127 + lis = tls.NewListener(lis, tc) 128 + 129 + err = srv.Serve(lis) 130 if err != nil { 131 return fmt.Errorf("error serving tls: %w", err) 132 } ··· 143 144 healthSrv.SetServingStatus("http", healthv1.HealthCheckResponse_SERVING) 145 146 + lis, err := net.Listen("tcp", s.cfg.httpsBind) 147 + if err != nil { 148 + return fmt.Errorf("error listening on %s: %v", s.cfg.httpsBind, err) 149 + } 150 + defer lis.Close() 151 + 152 + if s.cfg.proxyProto { 153 + lis = &proxyproto.Listener{Listener: lis} 154 + } 155 + 156 + err = srv.Serve(lis) 157 if err != nil { 158 return fmt.Errorf("error serving non-tls: %w", err) 159 }