package tcp import ( "bufio" "bytes" "errors" "io" "net" "sync" "github.com/rs/zerolog/log" tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp" "github.com/traefik/traefik/v3/pkg/tcp" ) var ( PostgresStartTLSMsg = []byte{0, 0, 0, 8, 4, 210, 22, 47} // int32(8) + int32(80877103) PostgresStartTLSReply = []byte{83} // S ) // isPostgres determines whether the buffer contains the Postgres STARTTLS message. func isPostgres(br *bufio.Reader) (bool, error) { // Peek the first 8 bytes individually to prevent blocking on peek // if the underlying conn does not send enough bytes. // It could happen if a protocol start by sending less than 8 bytes, // and expect a response before proceeding. for i := 1; i < len(PostgresStartTLSMsg)+1; i++ { peeked, err := br.Peek(i) if err != nil { var opErr *net.OpError if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || opErr.Timeout()) { log.Error().Err(err).Msg("Error while Peeking first byte") } return false, err } if !bytes.Equal(peeked, PostgresStartTLSMsg[:i]) { return false, nil } } return true, nil } // servePostgres serves a connection with a Postgres client negotiating a STARTTLS session. // It handles TCP TLS routing, after accepting to start the STARTTLS session. func (r *Router) servePostgres(conn tcp.WriteCloser) { _, err := conn.Write(PostgresStartTLSReply) if err != nil { conn.Close() return } br := bufio.NewReader(conn) b := make([]byte, len(PostgresStartTLSMsg)) _, err = br.Read(b) if err != nil { conn.Close() return } hello, err := clientHelloInfo(br) if err != nil { conn.Close() return } if !hello.isTLS { conn.Close() return } connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos) if err != nil { log.Error().Err(err).Msg("Error while reading TCP connection data") conn.Close() return } // Contains also TCP TLS passthrough routes. handlerTCPTLS, _ := r.muxerTCPTLS.Match(connData) if handlerTCPTLS == nil { conn.Close() return } // We are in TLS mode and if the handler is not TLSHandler, we are in passthrough. proxiedConn := r.GetConn(conn, hello.peeked) if _, ok := handlerTCPTLS.(*tcp.TLSHandler); !ok { proxiedConn = &postgresConn{WriteCloser: proxiedConn} } handlerTCPTLS.ServeTCP(proxiedConn) } // postgresConn is a tcp.WriteCloser that will negotiate a TLS session (STARTTLS), // before exchanging any data. // It enforces that the STARTTLS negotiation with the peer is successful. type postgresConn struct { tcp.WriteCloser starttlsMsgSent bool // whether we have already sent the STARTTLS handshake to the backend. starttlsReplyReceived bool // whether we have already received the STARTTLS handshake reply from the backend. // errChan makes sure that an error is returned if the first operation to ever // happen on a postgresConn is a Write (because it should instead be a Read). errChanMu sync.Mutex errChan chan error } // Read reads bytes from the underlying connection (tcp.WriteCloser). // On first call, it actually only injects the PostgresStartTLSMsg, // in order to behave as a Postgres TLS client that initiates a STARTTLS handshake. // Read does not support concurrent calls. func (c *postgresConn) Read(p []byte) (n int, err error) { if c.starttlsMsgSent { if err := <-c.errChan; err != nil { return 0, err } return c.WriteCloser.Read(p) } defer func() { c.starttlsMsgSent = true c.errChanMu.Lock() c.errChan = make(chan error) c.errChanMu.Unlock() }() copy(p, PostgresStartTLSMsg) return len(PostgresStartTLSMsg), nil } // Write writes bytes to the underlying connection (tcp.WriteCloser). // On first call, it checks that the bytes to write (the ones provided by the backend) // match the PostgresStartTLSReply, and if yes it drops them (as the STARTTLS // handshake between the client and traefik has already taken place). Otherwise, an // error is transmitted through c.errChan, so that the second Read call gets it and // returns it up the stack. // Write does not support concurrent calls. func (c *postgresConn) Write(p []byte) (n int, err error) { if c.starttlsReplyReceived { return c.WriteCloser.Write(p) } c.errChanMu.Lock() if c.errChan == nil { c.errChanMu.Unlock() return 0, errors.New("initial read never happened") } c.errChanMu.Unlock() defer func() { c.starttlsReplyReceived = true }() if len(p) != 1 || p[0] != PostgresStartTLSReply[0] { c.errChan <- errors.New("invalid response from Postgres server") return len(p), nil } close(c.errChan) return 1, nil }