@@ -20,8 +20,10 @@ import (
2020 "errors"
2121 "fmt"
2222 "log/slog"
23+ "net"
2324 "net/http"
2425 "net/http/httputil"
26+ "slices"
2527 "strings"
2628 "sync"
2729 "time"
@@ -51,6 +53,7 @@ type Bastion struct {
5153 c * Config
5254 proxy * httputil.ReverseProxy
5355 pool * backendConnectionsPool
56+ tls * tls.Config
5457}
5558
5659type keyHash [sha256 .Size ]byte
@@ -84,27 +87,14 @@ func New(c *Config) (*Bastion, error) {
8487 Transport : b .pool ,
8588 ErrorLog : slog .NewLogLogger (b .pool .log .Handler (), slog .LevelDebug ),
8689 }
87- return b , nil
88- }
89-
90- // ConfigureServer sets up srv to handle backend connections to the bastion. It
91- // wraps TLSConfig.GetConfigForClient to intercept backend connections, and sets
92- // TLSNextProto for the bastion ALPN protocol. The original tls.Config is still
93- // used for non-bastion backend connections.
94- //
95- // Note that since TLSNextProto won't be nil after a call to ConfigureServer,
96- // the caller might want to call [http2.ConfigureServer] as well.
97- func (b * Bastion ) ConfigureServer (srv * http.Server ) error {
98- if srv .TLSNextProto == nil {
99- srv .TLSNextProto = make (map [string ]func (* http.Server , * tls.Conn , http.Handler ))
100- }
101- srv .TLSNextProto ["bastion/0" ] = b .pool .handleBackend
102-
103- bastionTLSConfig := & tls.Config {
90+ b .tls = & tls.Config {
10491 MinVersion : tls .VersionTLS13 ,
10592 NextProtos : []string {"bastion/0" },
10693 ClientAuth : tls .RequireAnyClientCert ,
10794 VerifyConnection : func (cs tls.ConnectionState ) error {
95+ if cs .NegotiatedProtocol != "bastion/0" {
96+ return fmt .Errorf ("missing ALPN" )
97+ }
10898 h , err := backendHash (cs )
10999 if err != nil {
110100 return err
@@ -116,17 +106,47 @@ func (b *Bastion) ConfigureServer(srv *http.Server) error {
116106 },
117107 GetCertificate : b .c .GetCertificate ,
118108 }
109+ return b , nil
110+ }
111+
112+ // HandleBackendConnection handles a new backend connection.
113+ //
114+ // It can be used alternatively to [Bastion.ConfigureServer] to accept backend
115+ // connections on a dedicated listener.
116+ func (b * Bastion ) HandleBackendConnection (conn net.Conn ) {
117+ tlsConn := tls .Server (conn , b .tls )
118+ if err := tlsConn .Handshake (); err != nil {
119+ b .pool .log .Debug ("failed TLS handshake from backend" , "err" , err , "remote" , conn .RemoteAddr ())
120+ conn .Close ()
121+ return
122+ }
123+ b .pool .Handle (tlsConn )
124+ conn .Close ()
125+ }
126+
127+ // ConfigureServer sets up srv to handle backend connections to the bastion. It
128+ // wraps TLSConfig.GetConfigForClient to intercept backend connections, and sets
129+ // TLSNextProto for the bastion ALPN protocol. The original tls.Config is still
130+ // used for non-bastion backend connections.
131+ //
132+ // Note that since TLSNextProto won't be nil after a call to ConfigureServer,
133+ // the caller might want to call [http2.ConfigureServer] as well.
134+ func (b * Bastion ) ConfigureServer (srv * http.Server ) error {
135+ if srv .TLSNextProto == nil {
136+ srv .TLSNextProto = make (map [string ]func (* http.Server , * tls.Conn , http.Handler ))
137+ }
138+ srv .TLSNextProto ["bastion/0" ] = func (_ * http.Server , c * tls.Conn , _ http.Handler ) {
139+ b .pool .Handle (c )
140+ }
119141
120142 if srv .TLSConfig == nil {
121143 srv .TLSConfig = & tls.Config {}
122144 }
123145 oldGetConfigForClient := srv .TLSConfig .GetConfigForClient
124146 srv .TLSConfig .GetConfigForClient = func (chi * tls.ClientHelloInfo ) (* tls.Config , error ) {
125- for _ , proto := range chi .SupportedProtos {
126- if proto == "bastion/0" {
127- // This is a bastion connection from a backend.
128- return bastionTLSConfig , nil
129- }
147+ if slices .Contains (chi .SupportedProtos , "bastion/0" ) {
148+ // This is a bastion connection from a backend.
149+ return b .tls , nil
130150 }
131151 if oldGetConfigForClient != nil {
132152 return oldGetConfigForClient (chi )
@@ -212,7 +232,7 @@ func (p *backendConnectionsPool) RoundTrip(r *http.Request) (*http.Response, err
212232 return cc .RoundTrip (r )
213233}
214234
215- func (p * backendConnectionsPool ) handleBackend ( hs * http. Server , c * tls.Conn , h http. Handler ) {
235+ func (p * backendConnectionsPool ) Handle ( c * tls.Conn ) {
216236 backend , err := backendHash (c .ConnectionState ())
217237 if err != nil {
218238 p .log .Info ("failed to get backend hash" , "err" , err )
0 commit comments