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

Changed files
+48 -11
cmd
hythlodaeus
server
+2
cmd/hythlodaeus/main.go
··· 26 26 grpcKey = flag.String("grpc-key", "./var/tls.key", "the TLS key to serve grpc") 27 27 httpBind = flag.String("http-bind", ":80", "the host:port to bind plain HTTP") 28 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") 29 30 telemetryEnable = flag.Bool("telemetry-enable", false, "if true, enable request telemetry bundling to object storage") 30 31 telemetryBucket = flag.String("telemetry-bucket", "relayd-logs", "object storage bucket to dump logs to") 31 32 telemetryPathStyle = flag.Bool("telemetry-path-style", false, "if true, use s3 path style") ··· 60 61 server.WithGRPCKey(*grpcKey), 61 62 server.WithHTTPBind(*httpBind), 62 63 server.WithHTTPSBind(*httpsBind), 64 + server.WithProxyProto(*proxyProto), 63 65 server.WithTelemetryConfig(telemetry.Config{ 64 66 Bucket: *telemetryBucket, 65 67 PathStyle: *telemetryPathStyle,
+1
go.mod
··· 12 12 github.com/felixge/httpsnoop v1.0.4 13 13 github.com/google/uuid v1.6.0 14 14 github.com/joho/godotenv v1.5.1 15 + github.com/pires/go-proxyproto v0.8.1 15 16 github.com/prometheus/client_golang v1.22.0 16 17 golang.org/x/net v0.40.0 17 18 golang.org/x/sync v0.14.0
+2
go.sum
··· 259 259 github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 260 260 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 261 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= 262 264 github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 263 265 github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 264 266 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+8
server/config.go
··· 9 9 grpcInsecure bool 10 10 httpBind string 11 11 httpsBind string 12 + proxyProto bool 12 13 telemetryConfig telemetry.Config 13 14 } 14 15 ··· 20 21 grpcKey: "", 21 22 httpBind: ":80", 22 23 httpsBind: ":443", 24 + proxyProto: false, 23 25 } 24 26 } 25 27 ··· 59 61 func WithHTTPSBind(httpsBind string) Option { 60 62 return func(cfg *config) { 61 63 cfg.httpsBind = httpsBind 64 + } 65 + } 66 + 67 + func WithProxyProto(proxyProto bool) Option { 68 + return func(cfg *config) { 69 + cfg.proxyProto = proxyProto 62 70 } 63 71 } 64 72
+35 -11
server/server.go
··· 13 13 "git.xeserv.us/Techaro/hythlodaeus/telemetry" 14 14 "git.xeserv.us/Techaro/hythlodaeus/watcher" 15 15 "github.com/exaring/ja4plus" 16 + "github.com/pires/go-proxyproto" 16 17 "golang.org/x/net/http2" 17 18 "golang.org/x/sync/errgroup" 18 19 "google.golang.org/grpc" ··· 93 94 }) 94 95 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 + 96 107 srv := http.Server{ 97 108 Addr: s.cfg.httpsBind, 98 109 Handler: s.ja4m.Wrap(s.sink.Middleware(s)), 99 110 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 - }, 111 + TLSConfig: tc, 109 112 } 110 113 111 114 slog.Info("starting server", "protocol", "https", "addr", srv.Addr) 112 115 113 116 healthSrv.SetServingStatus("https", healthv1.HealthCheckResponse_SERVING) 114 117 115 - err := srv.ListenAndServeTLS("", "") 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) 116 130 if err != nil { 117 131 return fmt.Errorf("error serving tls: %w", err) 118 132 } ··· 129 143 130 144 healthSrv.SetServingStatus("http", healthv1.HealthCheckResponse_SERVING) 131 145 132 - err := srv.ListenAndServe() 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) 133 157 if err != nil { 134 158 return fmt.Errorf("error serving non-tls: %w", err) 135 159 }