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 }