github.com/Cloud-Foundations/Dominator@v0.3.4/lib/srpc/api.go (about)

     1  /*
     2  Package srpc is similar to the net/rpc package in the Go standard library,
     3  except that it provides streaming RPC access, TLS support and authentication
     4  and authorisation using X509 client certificates.
     5  
     6  Package srpc provides access to the exported methods of an object across a
     7  network or other I/O connection. A server registers an object, making it
     8  visible as a service with the name of the type of the object. After
     9  registration, exported methods of the object will be accessible remotely.
    10  A server may register multiple objects (services) of different types but it
    11  is an error to register multiple objects of the same type.
    12  
    13  The remainder of this documentation describes the protocol, to assist the
    14  development of implementations in other languages.
    15  
    16  Internally, multiple URL paths are registered with the HTTP default mux:
    17  
    18  	/_goSRPC_/              Unsecured (no TLS, no auth), GOB coder.
    19  	/_go_TLS_SRPC_/         Secured (TLS, full auth), GOB coder.
    20  	/_SRPC_/unsecured/JSON  Unsecured (no TLS, no auth), JSON coder.
    21  	/_SRPC_/TLS/JSON        Secured (TLS, full auth), JSON coder.
    22  
    23  Thus, a web server may also support SRPC on the same port.
    24  
    25  A client issues a HTTP CONNECT request to a server and (for secured
    26  connections) performs a TLS handshake. If the server requires the TLS
    27  handshake prior to the HTTP CONNECT, the client will retry with that mode.
    28  
    29  Once connected, a client may issue a sequence of RPC calls, one at a time
    30  per connection. The client sends the name of the RPC method to call,
    31  followed by a newline character (a carriage return+newline is permitted).
    32  For a secured connection, the server will verify if the client X509
    33  certificate is signed by a trusted CA and if the method is listed in the
    34  list of permitted methods in the certificate.
    35  If the method call is established, the server sends a newline character. If
    36  the method call is rejected then an error message followed by a newline is
    37  sent.
    38  
    39  The server then calls a registered method hander. The client and server can
    40  exchange messages using the appropriate coder (GOB is preferred, JSON is
    41  available as a fallback). Most method handlers wait for client messages and
    42  then respond. Once the method handler exits (without an error code), the
    43  server waits for another method call.
    44  */
    45  package srpc
    46  
    47  import (
    48  	"bufio"
    49  	"crypto/tls"
    50  	"crypto/x509"
    51  	"errors"
    52  	"flag"
    53  	stdlog "log"
    54  	"net"
    55  	"os"
    56  	"sync"
    57  	"time"
    58  
    59  	"github.com/Cloud-Foundations/Dominator/lib/log"
    60  	"github.com/Cloud-Foundations/Dominator/lib/log/debuglogger"
    61  	libnet "github.com/Cloud-Foundations/Dominator/lib/net"
    62  	"github.com/Cloud-Foundations/Dominator/lib/resourcepool"
    63  )
    64  
    65  var (
    66  	ErrorConnectionRefused    = errors.New("connection refused")
    67  	ErrorNoRouteToHost        = errors.New("no route to host")
    68  	ErrorMissingCA            = errors.New("missing CA")
    69  	ErrorMissingCertificate   = errors.New("missing certificate")
    70  	ErrorBadCertificate       = errors.New("bad certificate")
    71  	ErrorNoSrpcEndpoint       = errors.New("no SRPC endpoint")
    72  	ErrorAccessToMethodDenied = errors.New("access to method denied")
    73  
    74  	ErrorCloseClient = errors.New("close client")
    75  
    76  	// Interface check.
    77  	_ ClientI = (*Client)(nil)
    78  )
    79  
    80  var (
    81  	clientTlsConfig    *tls.Config
    82  	fullAuthCaCertPool *x509.CertPool
    83  	serverTlsConfig    *tls.Config
    84  	tlsRequired        bool
    85  
    86  	logger log.DebugLogger = debuglogger.New(
    87  		stdlog.New(os.Stderr, "", stdlog.LstdFlags))
    88  
    89  	srpcClientDoNotUseMethodPowers = flag.Bool("srpcClientDoNotUseMethodPowers",
    90  		false, "If true, do not use method powers when connecting to servers")
    91  	srpcProxy = flag.String("srpcProxy", "",
    92  		"Proxy to use (only works for some operations)")
    93  	srpcTrustVmOwners = flag.Bool("srpcTrustVmOwners", true,
    94  		"If true, trust the SmallStack VM owners for all method access")
    95  )
    96  
    97  // CheckTlsRequired returns true if the server requires TLS connections with
    98  // trusted certificates. It returns false if unencrypted or unauthenticated
    99  // connections are permitted (i.e. insecure mode).
   100  func CheckTlsRequired() bool {
   101  	return tlsRequired
   102  }
   103  
   104  // GetEarliestClientCertExpiration returns the earliest expiration time of any
   105  // certificate registered with RegisterClientTlsConfig. The zero value is
   106  // returned if there are no certificates with an expiration time.
   107  func GetEarliestClientCertExpiration() time.Time {
   108  	return getEarliestClientCertExpiration()
   109  }
   110  
   111  // GetNumPanicedCalls returns the number of server method calls which paniced.
   112  func GetNumPanicedCalls() uint64 {
   113  	return getNumPanicedCalls()
   114  }
   115  
   116  // LoadCertificates loads zero or more X.509 certificates from directory. Each
   117  // certificate must be stored in a pair of PEM-encoded files, with the private
   118  // key in a file with extension '.key' and the corresponding public key
   119  // certificate in a file with extension 'cert'. If there is an error loading a
   120  // certificate pair then processing stops and the error is returned.
   121  func LoadCertificates(directory string) ([]tls.Certificate, error) {
   122  	return loadCertificates(directory)
   123  }
   124  
   125  // LoadCertificatesFromMetadata will attempt to load an X.509 certificate and
   126  // key from the Metadata service. If errorIfMissing, an error will be returned
   127  // if data could not be loaded. If errorIfExpired, an error will be returned
   128  // if the certificate is not yet/no longer valid.
   129  func LoadCertificatesFromMetadata(timeout time.Duration, errorIfMissing bool,
   130  	errorIfExpired bool) (
   131  	*tls.Certificate, error) {
   132  	return loadCertificatesFromMetadata(timeout, errorIfMissing, errorIfExpired)
   133  }
   134  
   135  type AuthInformation struct {
   136  	GroupList        map[string]struct{}
   137  	HaveMethodAccess bool
   138  	Username         string
   139  }
   140  
   141  type ClientI interface {
   142  	Call(serviceMethod string) (*Conn, error)
   143  	Close() error
   144  	Ping() error
   145  	RequestReply(serviceMethod string, request interface{},
   146  		reply interface{}) error
   147  	SetKeepAlive(keepalive bool) error
   148  	SetKeepAlivePeriod(d time.Duration) error
   149  }
   150  
   151  // Dialer implements a dialer that can be use to create connections.
   152  type Dialer interface {
   153  	Dial(network, address string) (net.Conn, error)
   154  }
   155  
   156  type Decoder interface {
   157  	Decode(e interface{}) error
   158  }
   159  
   160  type Encoder interface {
   161  	Encode(e interface{}) error
   162  }
   163  
   164  type FakeClientOptions struct{}
   165  
   166  // MethodBlocker defines an interface to block method calls (after possible
   167  // authorisation) for a receiver (passed to RegisterName). This may be used to
   168  // attach rate limiting polcies for method calls.
   169  type MethodBlocker interface {
   170  	// BlockMethod is called after method access is granted, prior to calling
   171  	// the method. After the method call completes, the returned function is
   172  	// called. If this is nil, no function is called. If a non-nil error is
   173  	// returned then the method call is blocked and the remote caller will
   174  	// receive the error.
   175  	BlockMethod(methodName string, authInfo *AuthInformation) (func(), error)
   176  }
   177  
   178  // MethodGranter defines an interface to grant method calls (if access is not
   179  // granted by the built-in authorisation mechanism) for a receiver (passed to
   180  // RegisterName).
   181  type MethodGranter interface {
   182  	// GrantMethod is called to check if method access should be granted. If
   183  	// access should be granted, the method should return true.
   184  	GrantMethod(serviceMethod string, authInfo *AuthInformation) bool
   185  }
   186  
   187  // RegisterName publishes in the server the set of methods of the receiver
   188  // value that satisfy one of the following interfaces:
   189  //
   190  //	func Method(*Conn) error
   191  //	func Method(*Conn, Decoder, Encoder) error
   192  //	func Method(*Conn, request, *response) error
   193  //
   194  // The request/response method must not perform I/O on the Conn type. This is
   195  // passed only to provide access to connection metadata.
   196  // If rcvr implements MethodBlocker then the BlockMethod method will be called
   197  // as needed.
   198  // The name of the receiver (service) is given by name.
   199  func RegisterName(name string, rcvr interface{}) error {
   200  	return registerName(name, rcvr, ReceiverOptions{})
   201  }
   202  
   203  func RegisterNameWithOptions(name string, rcvr interface{},
   204  	options ReceiverOptions) error {
   205  	return registerName(name, rcvr, options)
   206  }
   207  
   208  // RegisterServerTlsConfig registers the configuration for TLS server
   209  // connections.
   210  // If requireTls is true, any non-TLS connection will be rejected.
   211  func RegisterServerTlsConfig(config *tls.Config, requireTls bool) {
   212  	serverTlsConfig = config
   213  	tlsRequired = requireTls
   214  }
   215  
   216  // RegisterClientTlsConfig registers the configuration for TLS client
   217  // connections.
   218  func RegisterClientTlsConfig(config *tls.Config) {
   219  	clientTlsConfig = config
   220  }
   221  
   222  // RegisterFullAuthCA registers the CA certificate pool used for full
   223  // authentication/authorisation checks (including method checks). If not
   224  // specified, the CA certificate pool registered with RegisterServerTlsConfig is
   225  // used for full auth checks. This allows for distinguishing between CAs trusted
   226  // for everything versus CAs trusted only for identity (username and groups).
   227  func RegisterFullAuthCA(certPool *x509.CertPool) {
   228  	fullAuthCaCertPool = certPool
   229  }
   230  
   231  type privateClientResource struct {
   232  	clientResource *ClientResource
   233  	tlsConfig      *tls.Config
   234  	dialer         Dialer
   235  }
   236  
   237  type ClientResource struct {
   238  	network               string
   239  	address               string
   240  	resource              *resourcepool.Resource
   241  	privateClientResource privateClientResource
   242  	client                *Client
   243  	inUse                 bool
   244  	closeError            error
   245  }
   246  
   247  // SetDefaultGrantMethod registers the grantMethod function which will be
   248  // called to grant access to methods (if access is not granted by the built-in
   249  // authorisation mechanism) for all receivers. This is overridden by receivers
   250  // which implement the MethodGranter interface.
   251  // The default is to not grant access to methods (if the built-in authorisation
   252  // mechanism does not grant access).
   253  func SetDefaultGrantMethod(grantMethod func(serviceMethod string,
   254  	authInfo *AuthInformation) bool) {
   255  	defaultGrantMethod = grantMethod
   256  }
   257  
   258  // SetDefaultLogger will override the default logger used.
   259  func SetDefaultLogger(l log.DebugLogger) {
   260  	logger = l
   261  }
   262  
   263  // NewClientResource returns a ClientResource which may be later used to Get*
   264  // a Client which is part of a managed pool of connection slots (to limit
   265  // consumption of resources such as file descriptors). Clients can be released
   266  // with the Put method but the underlying connection may be kept open for later
   267  // re-use. The Client is placed on an internal list.
   268  // A typical programming pattern is:
   269  //
   270  //	cr := NewClientResource(...)
   271  //	c := cr.GetHttp(...)
   272  //	defer c.Put()
   273  //	if err { c.Close() }
   274  //	c := cr.GetHttp(...)
   275  //	defer c.Put()
   276  //	if err { c.Close() }
   277  //
   278  // This pattern ensures Get* and Put are always matched, and if there is a
   279  // communications error, Close shuts down the client so that a subsequent Get*
   280  // creates a new connection.
   281  func NewClientResource(network, address string) *ClientResource {
   282  	return newClientResource(network, address)
   283  }
   284  
   285  // GetHTTP is similar to DialHTTP except that the returned Client is part of a
   286  // managed pool of connection slots (to limit consumption of resources such as
   287  // file descriptors). GetHTTP will wait until a resource is available or
   288  // a message is received on cancelChannel. If cancelChannel is nil then GetHTTP
   289  // will wait indefinitely until a resource is available. If the wait is
   290  // cancelled then GetHTTP will return ErrorResourceLimitExceeded. The timeout
   291  // specifies how long to wait (after a resource is available) to make the
   292  // connection. If timeout is zero or less, the underlying OS timeout is used
   293  // (typically 3 minutes for TCP).
   294  func (cr *ClientResource) GetHTTP(cancelChannel <-chan struct{},
   295  	timeout time.Duration) (*Client, error) {
   296  	return cr.getHTTP(clientTlsConfig, cancelChannel,
   297  		&net.Dialer{Timeout: timeout})
   298  }
   299  
   300  // GetHTTPWithDialer is similar to GetHTTP except that the dialer is used to
   301  // create the underlying connection.
   302  func (cr *ClientResource) GetHTTPWithDialer(cancelChannel <-chan struct{},
   303  	dialer Dialer) (*Client, error) {
   304  	return cr.getHTTP(clientTlsConfig, cancelChannel, dialer)
   305  }
   306  
   307  // GetTlsHTTP is similar to DialTlsHTTP but returns a Client that is part of a
   308  // managed pool like the GetHTTP method returns.
   309  func (cr *ClientResource) GetTlsHTTP(tlsConfig *tls.Config,
   310  	cancelChannel <-chan struct{}, timeout time.Duration) (*Client, error) {
   311  	return cr.GetTlsHTTPWithDialer(tlsConfig, cancelChannel,
   312  		&net.Dialer{Timeout: timeout})
   313  }
   314  
   315  // GetTlsHTTPWithDialer is similar to GetTlsHTTP except that the dialer is used
   316  // to create the underlying connection.
   317  func (cr *ClientResource) GetTlsHTTPWithDialer(tlsConfig *tls.Config,
   318  	cancelChannel <-chan struct{}, dialer Dialer) (*Client, error) {
   319  	if tlsConfig == nil {
   320  		tlsConfig = clientTlsConfig
   321  	}
   322  	return cr.getHTTP(tlsConfig, cancelChannel, dialer)
   323  }
   324  
   325  func (cr *ClientResource) ScheduleClose() {
   326  	cr.resource.ScheduleRelease()
   327  }
   328  
   329  type Client struct {
   330  	bufrw             *bufio.ReadWriter
   331  	callLock          sync.Mutex
   332  	conn              net.Conn
   333  	connType          string // Human-readable.
   334  	fakeClientOptions *FakeClientOptions
   335  	isEncrypted       bool
   336  	localAddr         string
   337  	makeCoder         coderMaker
   338  	remoteAddr        string
   339  	resource          *ClientResource
   340  	tcpConn           libnet.TCPConn // The underlying raw TCP connection (if TCP).
   341  }
   342  
   343  // DialHTTP connects to an HTTP SRPC server at the specified network address
   344  // listening on the HTTP SRPC path. If timeout is zero or less, the underlying
   345  // OS timeout is used (typically 3 minutes for TCP).
   346  func DialHTTP(network, address string, timeout time.Duration) (*Client, error) {
   347  	return dialHTTP(network, address, clientTlsConfig,
   348  		&net.Dialer{Timeout: timeout})
   349  }
   350  
   351  // DialHTTPWithDialer is similar to DialHTTP except that the dialer is used to
   352  // create the underlying connection.
   353  func DialHTTPWithDialer(network, address string, dialer Dialer) (
   354  	*Client, error) {
   355  	return dialHTTP(network, address, clientTlsConfig, dialer)
   356  }
   357  
   358  // DialTlsHTTP connects to an HTTP SRPC TLS server at the specified network
   359  // address listening on the HTTP SRPC TLS path. If timeout is zero or less, the
   360  // underlying OS timeout is used (typically 3 minutes for TCP).
   361  func DialTlsHTTP(network, address string, tlsConfig *tls.Config,
   362  	timeout time.Duration) (
   363  	*Client, error) {
   364  	if tlsConfig == nil {
   365  		tlsConfig = clientTlsConfig
   366  	}
   367  	return DialTlsHTTPWithDialer(network, address, tlsConfig,
   368  		&net.Dialer{Timeout: timeout})
   369  }
   370  
   371  // DialTlsHTTPWithDialer is similar to DialTlsHTTP except that the dialer is
   372  // used to create the underlying connection.
   373  func DialTlsHTTPWithDialer(network, address string, tlsConfig *tls.Config,
   374  	dialer Dialer) (
   375  	*Client, error) {
   376  	if tlsConfig == nil {
   377  		tlsConfig = clientTlsConfig
   378  	}
   379  	return dialHTTP(network, address, tlsConfig, dialer)
   380  }
   381  
   382  // NewFakeClient will return a fake Client which may be used for limited
   383  // testing. The Client will support the Close method. Other methods may panic.
   384  func NewFakeClient(options FakeClientOptions) *Client {
   385  	return newFakeClient(options)
   386  }
   387  
   388  // Close will close a client, immediately releasing the internal connection.
   389  func (client *Client) Close() error {
   390  	return client.close()
   391  }
   392  
   393  // Call opens a buffered connection to the named Service.Method function, and
   394  // returns a connection handle and an error status. The connection handle wraps
   395  // a *bufio.ReadWriter. Only one connection can be made per Client. The Call
   396  // method will block if another Call is in progress. The Close method must be
   397  // called prior to attempting another Call.
   398  func (client *Client) Call(serviceMethod string) (*Conn, error) {
   399  	return client.call(serviceMethod)
   400  }
   401  
   402  // IsClosed will return true of the client is closed.
   403  func (client *Client) IsClosed() bool {
   404  	return client.conn == nil
   405  }
   406  
   407  // IsEncrypted will return true if the underlying connection is TLS-encrypted.
   408  func (client *Client) IsEncrypted() bool {
   409  	return client.isEncrypted
   410  }
   411  
   412  // IsFromClientResource will return true if the Client was created from a
   413  // ClientResource.
   414  func (client *Client) IsFromClientResource() bool {
   415  	return client.resource != nil
   416  }
   417  
   418  // Ping sends a short "are you alive?" request and waits for a response. No
   419  // method permissions are required for this operation. The Ping method is a
   420  // wrapper around the Call method and hence will block if a Call is already in
   421  // progress.
   422  func (client *Client) Ping() error {
   423  	return client.ping()
   424  }
   425  
   426  // Put releases a client that was previously created using one of the Get*
   427  // methods. It may be internally closed later if required to free limited
   428  // resources (such as file descriptors). No methods may be called after Put is
   429  // called. If Put is called after Close, no action is taken (this is a safe
   430  // operation and is commonly used in some programming patterns).
   431  func (client *Client) Put() {
   432  	client.put()
   433  }
   434  
   435  // SetKeepAlive sets whether the operating system should send keepalive messages
   436  // on the connection.
   437  func (client *Client) SetKeepAlive(keepalive bool) error {
   438  	return client.setKeepAlive(keepalive)
   439  }
   440  
   441  // SetKeepAlivePeriod sets the period between keepalive messages.
   442  func (client *Client) SetKeepAlivePeriod(d time.Duration) error {
   443  	return client.setKeepAlivePeriod(d)
   444  }
   445  
   446  // RequestReply sends a request message to the named Service.Method function,
   447  // and waits for a reply. The request and reply messages are GOB encoded and
   448  // decoded, respectively. This method is a convenience wrapper around the Call
   449  // method.
   450  func (client *Client) RequestReply(serviceMethod string, request interface{},
   451  	reply interface{}) error {
   452  	return client.requestReply(serviceMethod, request, reply)
   453  }
   454  
   455  type Conn struct {
   456  	Decoder
   457  	Encoder
   458  	*bufio.ReadWriter
   459  	conn             net.Conn
   460  	groupList        map[string]struct{}
   461  	haveMethodAccess bool
   462  	isEncrypted      bool
   463  	localAddr        string
   464  	parent           *Client             // nil: server-side connection.
   465  	permittedMethods map[string]struct{} // nil: all, empty: none permitted.
   466  	releaseNotifier  func()
   467  	remoteAddr       string
   468  	username         string // Empty string for unauthenticated.
   469  }
   470  
   471  // Close will close the connection to the Sevice.Method function, releasing the
   472  // Client for a subsequent Call.
   473  func (conn *Conn) Close() error {
   474  	return conn.close()
   475  }
   476  
   477  // GetAuthInformation will return authentication information for the client who
   478  // holds the certificate used to authenticate the connection to the server. If
   479  // the connection was not authenticated nil is returned. If the connection is a
   480  // client connection, then GetAuthInformation will panic.
   481  func (conn *Conn) GetAuthInformation() *AuthInformation {
   482  	return conn.getAuthInformation()
   483  }
   484  
   485  // GetCloseNotifier will create a goroutine which reads from the connection
   486  // until it closes or there is a read error. The error (which is nil if the
   487  // connection closed) is sent to the channel. All data read are discarded.
   488  func (conn *Conn) GetCloseNotifier() <-chan error {
   489  	return conn.getCloseNotifier()
   490  }
   491  
   492  // IsEncrypted will return true if the underlying connection is TLS-encrypted.
   493  func (conn *Conn) IsEncrypted() bool {
   494  	return conn.isEncrypted
   495  }
   496  
   497  // RemoteAddr returns the remote network address.
   498  func (conn *Conn) RemoteAddr() string {
   499  	return conn.remoteAddr
   500  }
   501  
   502  // RequestReply sends a request message to a connection and waits for a reply.
   503  // The request and reply messages are GOB encoded and decoded, respectively.
   504  func (conn *Conn) RequestReply(request interface{}, reply interface{}) error {
   505  	return conn.requestReply(request, reply)
   506  }
   507  
   508  // Username will return the username of the client who holds the certificate
   509  // used to authenticate the connection to the server. If the connection was not
   510  // authenticated the empty string is returned. If the connection is a client
   511  // connection, then Username will panic.
   512  func (conn *Conn) Username() string {
   513  	return conn.getUsername()
   514  }
   515  
   516  type ReceiverOptions struct {
   517  	PublicMethods []string
   518  }