github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/cert/logging_conn.go (about)

     1  // logging_conn.go defines LoggingListener, an implementation of net.Listener
     2  // that allows tests to read the text transported over a client-server
     3  // connection, and confirm, in the case of a TLS connection, that nothing
     4  // sensitive was transported in plain text.
     5  
     6  package cert
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"io"
    12  	"net"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    17  )
    18  
    19  type loggingPipeAddr struct{}
    20  
    21  func (o loggingPipeAddr) Network() string {
    22  	return "logging-pipe"
    23  }
    24  
    25  func (o loggingPipeAddr) String() string {
    26  	return "logging-pipe"
    27  }
    28  
    29  // loggingPipe is a struct containing two buffers, which log all of the traffic
    30  // passing through LoggingPipe in each direction
    31  type loggingPipe struct {
    32  	ClientToServerBuf, ServerToClientBuf bytes.Buffer
    33  
    34  	clientReader, serverReader io.Reader
    35  	clientWriter, serverWriter io.WriteCloser
    36  }
    37  
    38  // newLoggingPipe initializes a loggingPipe
    39  func newLoggingPipe() *loggingPipe {
    40  	p := &loggingPipe{}
    41  	p.clientReader, p.clientWriter = io.Pipe()
    42  	p.clientReader = io.TeeReader(p.clientReader, &p.ServerToClientBuf)
    43  	p.serverReader, p.serverWriter = io.Pipe()
    44  	p.serverReader = io.TeeReader(p.serverReader, &p.ClientToServerBuf)
    45  	return p
    46  }
    47  
    48  // Close closes 'l' (no more reading/writing will be possible)
    49  func (p *loggingPipe) Close() error {
    50  	p.clientWriter.Close()
    51  	p.serverWriter.Close()
    52  	return nil
    53  }
    54  
    55  // clientConn returns a loggingConn at the oposite end of the loggingPipe as
    56  // serverConn. There is no fundamental difference between the clientConn and
    57  // the serverConn, as communication is full duplex, but distinguishing the two
    58  // ends of the pipe as client and server, rather than e.g. left and right,
    59  // hopefully makes the calling code easier to read
    60  func (p *loggingPipe) clientConn() *loggingConn {
    61  	return &loggingConn{
    62  		pipe: p,
    63  		r:    p.clientReader,
    64  		w:    p.serverWriter,
    65  	}
    66  }
    67  
    68  // serverConn returns a loggingConn at the opposite end of the loggingPipe of
    69  // clientConn (see the clientConn description for more information)
    70  func (p *loggingPipe) serverConn() *loggingConn {
    71  	return &loggingConn{
    72  		pipe: p,
    73  		r:    p.serverReader,
    74  		w:    p.clientWriter,
    75  	}
    76  }
    77  
    78  // loggingConn is an implementation of net.Conn that communicates with another
    79  // party over a loggingPipe.
    80  type loggingConn struct {
    81  	// pipe is the loggingPipe over which this connection is communicating
    82  	pipe *loggingPipe
    83  	r    io.Reader
    84  	w    io.WriteCloser
    85  }
    86  
    87  // Read implements the corresponding method of net.Conn
    88  func (l *loggingConn) Read(b []byte) (n int, err error) {
    89  	return l.r.Read(b)
    90  }
    91  
    92  // Write implements the corresponding method of net.Conn
    93  func (l *loggingConn) Write(b []byte) (n int, err error) {
    94  	return l.w.Write(b)
    95  }
    96  
    97  // Close implements the corresponding method of net.Conn
    98  func (l *loggingConn) Close() error {
    99  	return l.pipe.Close()
   100  }
   101  
   102  // LocalAddr implements the corresponding method of net.Conn
   103  func (l *loggingConn) LocalAddr() net.Addr {
   104  	return loggingPipeAddr{}
   105  }
   106  
   107  // RemoteAddr implements the corresponding method of net.Conn
   108  func (l *loggingConn) RemoteAddr() net.Addr {
   109  	return loggingPipeAddr{}
   110  }
   111  
   112  // SetDeadline implements the corresponding method of net.Conn
   113  func (l *loggingConn) SetDeadline(t time.Time) error {
   114  	return errors.New("not implemented")
   115  }
   116  
   117  // SetReadDeadline implements the corresponding method of net.Conn
   118  func (l *loggingConn) SetReadDeadline(t time.Time) error {
   119  	return errors.New("not implemented")
   120  }
   121  
   122  // SetWriteDeadline implements the corresponding method of net.Conn
   123  func (l *loggingConn) SetWriteDeadline(t time.Time) error {
   124  	return errors.New("not implemented")
   125  }
   126  
   127  // TestListener implements the net.Listener interface, returning loggingConns
   128  type TestListener struct {
   129  	// conn is the first (and last) non-nil connection returned from a call to
   130  	// Accept()
   131  	conn   *loggingConn
   132  	connMu sync.Mutex
   133  
   134  	// connCh provides connections (or nil) to Accept()
   135  	connCh chan net.Conn
   136  }
   137  
   138  // NewTestListener initializes and returns a new TestListener. To create
   139  // a new connection that that this Listener will serve on, call Dial(). To see
   140  // the logged communication over that connection's pipe, see ClientToServerLog
   141  // and ServerToClientLog
   142  func NewTestListener() *TestListener {
   143  	return &TestListener{
   144  		connCh: make(chan net.Conn),
   145  	}
   146  }
   147  
   148  // Dial initializes a new connection and releases a blocked call to Accept()
   149  func (l *TestListener) Dial(context.Context, string, string) (net.Conn, error) {
   150  	l.connMu.Lock()
   151  	defer l.connMu.Unlock()
   152  	if l.conn != nil {
   153  		return nil, errors.New("Dial() has already been called on this TestListener")
   154  	}
   155  
   156  	// Initialize logging pipe
   157  	p := newLoggingPipe()
   158  	l.conn = p.serverConn()
   159  
   160  	// send serverConn to Accept() and close l.connCh (so future callers to
   161  	// Accept() get nothing)
   162  	l.connCh <- p.serverConn()
   163  	close(l.connCh)
   164  	return p.clientConn(), nil
   165  }
   166  
   167  // ClientToServerLog returns the log of client -> server communication over the
   168  // first (and only) connection spawned by this listener
   169  func (l *TestListener) ClientToServerLog() []byte {
   170  	return l.conn.pipe.ClientToServerBuf.Bytes()
   171  }
   172  
   173  // ServerToClientLog the log of server -> client communication over the first
   174  // (and only) connection spawned by this listener
   175  func (l *TestListener) ServerToClientLog() []byte {
   176  	return l.conn.pipe.ServerToClientBuf.Bytes()
   177  }
   178  
   179  // Accept implements the corresponding method of net.Listener for
   180  // TestListener
   181  func (l *TestListener) Accept() (net.Conn, error) {
   182  	conn := <-l.connCh
   183  	if conn == nil {
   184  		return nil, errors.New("Accept() has already been called on this TestListener")
   185  	}
   186  	return conn, nil
   187  }
   188  
   189  // Close implements the corresponding method of net.Listener for
   190  // TestListener. Any blocked Accept operations will be unblocked and return
   191  // errors.
   192  func (l *TestListener) Close() error {
   193  	l.connMu.Lock()
   194  	defer l.connMu.Unlock()
   195  	c := <-l.connCh
   196  	if c != nil {
   197  		close(l.connCh)
   198  	}
   199  	return nil
   200  }
   201  
   202  // Addr implements the corresponding method of net.Listener for
   203  // TestListener
   204  func (l *TestListener) Addr() net.Addr {
   205  	return loggingPipeAddr{}
   206  }