lrpc

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2021 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package lrpc defines various interfaces and types used in communication with Centrify Client

Porting to different operating systems

The current implementation is implemented as tested in Linux. System dependent code are implemented in the files peer_linux.go and network_linux.go; and you need to provide equivalent functionalities for other platforms.

File session_linux.go

 This file implements all OS dependent functions defined in the SessionCtxt interface.  You need to implement the factory function
 NewSessionCtxt() and the following functions in SessionCtxt interface:

	// IsPrivileged() returns true if the peer process runs as privileged process.
	// In *nix, this means the effective UID is root (0)
	// In windows, this means the owner of process has local admin right
	IsPrivileged() (bool, error)

	// GetProcessID() returns the process ID of the peer process
	GetProcessID() (int32, error)

	// GetProgram() returns the name of program executable that the peer process is running with
	GetProgram() (string, error)

 Note that SessionCtxtBase is a helper struct that provides OS independent support for SessionCtxt.  More importantly, it implements the
 Get() and Set() methods for managing session related information.

File network_linux.go

// This file implements the OS-dependent code for listening and connecting to the service endpoint.  You need to implement the following functions:
//
// StartListener is OS-dependent function to establish an internal listener.
// Input parameter:
// endpoint (string):          The name of the endpoint
// acl (AccessControlLevel):   The access control level to start listening
//
// Return values:
// net.Listener:	network listener object established.
// error
//
// Note:
// This function should remove the endpoint if it already exists.

func StartListener(endpoint string, acl AccessControlLevel) (net.Listener, error)

// ConnectToServer is OS-dependent code for connecting to an internal endpoint.
// It is used by a RPC client.
//
// Input parameters:
// endpoint (string):          The endpoint to connect to.
// acl (AccessControlLevel):   The ACL for establishing the listener
//
// Return values:
// net.Conn:   The established network connection.
// error:		err

func ConnectToServer(endpoint string) (net.Conn, error)

Writing a RPC session server

You need to consider the followings when you implement a LRPC session server that serves all the RPC messages to an endpoint.

1. The process model. This depends on the nature of the messages/services. For most cases, it can be done simply by having a goroutine to serve all the messages for a LRPC connection. However, for NSS-related messages in Linux, it may be better to use a pre-configured pool of goroutines to serve all the requests as this will throttle such requests. This may or may not be necessary. Need to analyze performance test results first.

2. Message handling. For Windows, we may not need to protect the messages from snooping, and there may not be any need to implement sequence numbering and signing of request/request. However, we need to do this in *nix for security reasons.

This package currently provide a function that creates a RPC session server:

NewLrpc2SessionServer - creates a session server that uses a goroutine to serve all the messages for each LRPC connection.

Messages

A message can be identified by message ID. You can choose any message number that you like for your messages. The only limitations are:

1. The mesasge ID MUST be unique for each endpoint.

2. Message ID must convert correctly into an uint16 value.

You need to register all the messages for an endpoint before you invoke the Start() method.

Message handler

You need to implement a handler for each message as type CommandHandler. See description on type CommandHandler for details.

Writing application that needs LRPC service

1. Create a client connection by calling NewLrpc2ClientSession().

2. Connect to the client using Connect()

3. Set up the arguments for the LRPC call as an array of interface{}.

4. Call lrpc.DoRequest() (if a response is expected) or lrpc.DoAsyncRequest() (if no response is expected).

5. When lrpc.DoRequest() is called, the results are passed back as an array of interface{}. Process the results.

Steps in writing a session server for a specific endpoint

1. Call NewLrpc2SessionServer() (or provide your own function to create a session handler that implements the SessionServer interface).

  1. Register the messages for the handler by calling one or more of the followings on the created message handler: RegisterMsgsByID() RegisterMsgByID()

3. Call the Start() method of the handler to start the service.

4. Call the Stop() method of the handler to stop the service.

5. Call the Wait() method of the handler to wait for the service to be completely shutdown.

6. Implement a handler function for each message.

  1. If you need to save information for use for other messages sharing the same lrpc session, you can use the functions Get() and Set() in the associated SessionCtxt interface.

Index

Constants

View Source
const (
	LrpcMsgIDClientInfo            = 119
	Lrpc2MsgIDAdminClientGetToken  = 1500
	Lrpc2MsgIDGetPublicKey         = 1501
	Lrpc2MsgIDGetResourceOwnerCred = 1502
	Lrpc2MsgGetHashicorpVaultToken = 1503
)

Message ID

Variables

View Source
var (
	ErrLrpc2BadMagicNum       = errors.New("Bad magic number")
	ErrLrpc2BadHeaderLen      = errors.New("Bad header length")
	ErrLrpc2MsgVerMismatch    = errors.New("Message version mismatched")
	ErrLrpc2BadMsgLen         = errors.New("Bad message length")
	ErrLrpc2ProcessIDMismatch = errors.New("Process ID mismatched")
	ErrLrpc2SeqNumMismatch    = errors.New("Sequence number mismatched")
	ErrLrpc2NameNotSupported  = errors.New("Send command by name is not supported")
	ErrLrpc2MsgTooLong        = errors.New("Message length exceeds LRPC2 limit")
	ErrLrpc2PELocalUser       = errors.New("Local User is not supported")
	ErrLrpc2TypeNotSupported  = errors.New("Data type not supported")
)

LRPC2 errors

View Source
var (
	ErrMsgUnexpectedData   error = errors.New("Lrpc message contain unexpected data")
	ErrMsgIncorrectType    error = errors.New("Incorrect data type found in lrpc message")
	ErrMsgAtMsgEnd         error = errors.New("Already at end of lrpc message")
	ErrMsgNotComplete      error = errors.New("Lrpc message is incomplete")
	ErrMsgEncTypeNoSupp    error = errors.New("Enc type is not suported for encrypt/decrypt lrpc message")
	ErrMsgEncError         error = errors.New("Error while encrypt/decrypt protected lrpc message")
	ErrMsgInvalid          error = errors.New("Lrpc Message is invalid")
	ErrMsgBadHandshakeSize error = errors.New("LRPC2 handshake request size mismatched")
	ErrMsgBadVersion       error = errors.New("Unknown LRPC2 version")
	ErrMsgBadContext       error = errors.New("Incorrect response context")
)

Message status while Processing LRPC2 Message, these have same status name as 'C' part.

View Source
var (
	ErrLrpcServerCommandOutOfRange = errors.New("Command out of supported range")
	ErrLrpcServerNotConnected      = errors.New("Not connected")
	ErrLrpcServerAlreadyConnected  = errors.New("Already connected")
)

Common server errors

Functions

func ConnectToServer

func ConnectToServer(endpoint string) (net.Conn, error)

ConnectToServer is OS-dependent code for connecting to an internal endpoint. It is used by a RPC client.

 Input parameters:
 	endpoint (string):   The endpoint to connect to.

 Return values:
 	net.Conn:   The established network connection.
	error:		err

func DoAsyncRequest

func DoAsyncRequest(cl MessageClient, cmd interface{}, args []interface{}) error

DoAsyncRequest sends a command to the LRPC server. It does not wait for any response.

Input parameters:
 cl:  Pointer to an object that implements the MessageClient interface.
 cmd: command to use
 args: arguments for command (stored as an array)

Return value:
 error: error information

func DoRequest

func DoRequest(cl MessageClient, cmd interface{}, args []interface{}) ([]interface{}, error)

DoRequest sends a command to the LRPC server and waits for the response

Input parameters:
 cl:  Pointer to an object that implements the MessageClient interface.
 cmd: command to use
 args: arguments for command (stored as an array)

Return values:
 results:  results as an array
 error: error information

func StartListener

func StartListener(endpoint string, acl AccessControlLevel) (net.Listener, error)

StartListener is OS-dependent function to establish an internal listener.

 Input parameter:
 	endpoint (string):          The name of the endpoint
	acl (AccessControlLevel):   The ACL for starting listener, normally is null for linux

 Return values:
 	net.Listener:	network listener object established.
 	error

Note:

This function should remove the endpoint if it already exists.

Types

type AccessControlLevel

type AccessControlLevel interface{}

AccessControlLevel provides Access Control Level for creating listener. The is platform specific that how to use this data

type CommandHandler

type CommandHandler func(ctxt SessionCtxt, args []interface{}) []interface{}

CommandHandler is the function prototype of a handler that handles a LRPC message.

 Input parameters:
    ctxt: The context of the session.  It implements the SessionCtxt interface.
          The handler can call methods in this interface to get information about the requester,
          as wells managing information across multiple messages sharing the same LRPC session.
    args: An array of interfaces that contains the arguments for the command.

 Output:
	result:  An array of interfaces that contains the results for the command.

Notes for CommandHandler:

1. Each element of the arguments and results can have its own type. The command handler can validate the types of each argument to verify it is what it expects. Similarly, the handler is responsible for using the correct type of each element in the result array.

2. The command handler MUST return nil if the client does not expect any response.

3. String type argument/result MUST NOT contain the null character ('\x00')

type Lrpc2SessionServer

type Lrpc2SessionServer struct {
	// contains filtered or unexported fields
}

Lrpc2SessionServer defines a simple session server.

Characteristics:

  1. The process model is to have a separate goroutine that serves each incoming connection.
  2. Each connection may serve one RPC message at a time.
  3. Client/Server must close the connection upon error.
  4. It embeds struct svrBase which implements basic message registration and dispatching functionalities.

func (*Lrpc2SessionServer) RegisterMsgByID

func (s *Lrpc2SessionServer) RegisterMsgByID(k uint16, v interface{}) error

RegisterMsgByID() registers the handler for a single message by message ID

func (*Lrpc2SessionServer) RegisterMsgsByID

func (s *Lrpc2SessionServer) RegisterMsgsByID(m map[uint16]interface{}) error

RegisterMsgsByID registers handlers for multiple messages (identified by message ID)

func (*Lrpc2SessionServer) Start

func (s *Lrpc2SessionServer) Start() error

Start starts a goroutine that starts the session server.

If the listener starts successfully, the actual process is done as a goroutine, and this function returns immediately. Otherwise, returns error.

func (*Lrpc2SessionServer) Stop

func (s *Lrpc2SessionServer) Stop() error

Stop stops the session server. It stops receiving new connection requests. Existing connections will complete processing of the current request and then exits.

func (*Lrpc2SessionServer) Wait

func (s *Lrpc2SessionServer) Wait() error

Wait waits for all the goroutines used in the session server to exit.

type MessageClient

type MessageClient interface {
	//	Connect initiates a connection to the remote endpoint (specified when the MessageClient is created)
	Connect() error

	// WriteRequest sends a request message to the server.
	// Parameters:
	// cmd:	Command to send (only support uint32 and string type)
	// args:	Command arguments
	WriteRequest(cmd interface{}, args []interface{}) error

	// ReadResponse:	read the response
	// Return values:
	// 	results:	results in the response
	// 	err:		error
	ReadResponse() (results []interface{}, err error)

	// IsNamedMessagesSupported() returns a bool to indicate whether calling messages by name is supported.
	// Return value:
	// 	bool:	whether calling messages by name is supported
	IsNamedMessagesSupported() bool

	// Close() closes the client connection to the server
	// Return value:
	// 	err:	error
	Close() error
}

MessageClient defines all the required functions that a message client must implement.

Notes:

1. The implementation needs to ensure that ReadResponse() will return the correct response for the previous WriteRequest()/WriteNamedRequest().

2. For simplicity, the implementations assume that only one thread/goroutine can run WriteRequest()/ReadRequest(). If the object is shared, the caller MUST synchronize access to the object.

3. Legacy LRPC2 message protocol DOES NOT support calling messages by name. In this case, the first parameter in WriteRequest parameter must be uint32

func NewLrpc2ClientSession

func NewLrpc2ClientSession(endpoint string) MessageClient

NewLrpc2ClientSession create a new LRPC2 client session object.

type MessageServer

type MessageServer interface {
	// Accept() accepts a client connection and returns a new object that represents the accepted connection and supports the MessageServer interface.
	// 	Return values:
	// 	newServer:	New object that supports the MessageServer interface
	// 	err:		error
	Accept() (MessageServer, error)

	// GetSessionCtxt() returns the context for the LRPC session
	// 	Return values:
	// 	peer:	object that supports the SessionCtxt interface
	// 	err:	error
	GetSessionCtxt() (SessionCtxt, error)

	// ReadRequest() reads a request.
	// 	Return values:
	// 	msg:		opaque context for the message.  Need to be passed to WriteResponse()
	// 	command:	command ID
	// 	args:		arguments for the command
	// 	err:		error
	ReadRequest() (msg interface{}, command interface{}, args []interface{}, err error)

	// WriteResponse() writes the resposne to a command.
	// 	Parameters:
	// 	msg:		message context as received in ReadRequest
	//  command:		command ID
	// 	results:	results for the command.
	// 	Return value:
	// 	err:		error
	WriteResponse(msg interface{}, command interface{}, results []interface{}) error

	// IsNamedMessagesSupported() returns a bool to indicate whether calling messages by name is supported.
	// 	Return values:
	// 	bool:	whether calling messages by name is supported
	IsNamedMessagesSupported() bool

	// Close() closes the network connection associated with the current server connection
	Close() error
}

MessageServer defines all the required functions that a message server must implement.

Notes:

1. 'msg' is used to associate a response to the original request. It should be treated as opaque for the callers to ReadRequest/WriteResponse.

2. For simplicity, the implemenations do not serialize access to ReadRequest()/WriteResponse(). It is up to the caller to synchronize access if necessary.

type SessionCtxt

type SessionCtxt interface {

	// IsPrivileged() returns true if the peer process runs as privileged process.
	// In *nix, this means the effective UID is root (0)
	IsPrivileged() (bool, error)

	// GetProcessID() returns the process ID of the peer process
	GetProcessID() (int32, error)

	// GetProgram() returns the name of program executable that the peer process is running with
	GetProgram() (string, error)

	// GetCallerUserID() returns user ID of the caller.
	// For Linux, it is the string representation of the UID
	// For windows, it is the SID in string format
	GetCallerUserID() (string, error)

	// Get() gets an attribute associated with the object.  Both key and returned value are
	// application dependent and are defined by the command handlers
	Get(key string) interface{}

	// Set() sets an attribute associated with the object.  The key and val are application dependent.
	Set(key string, val interface{})
}

SessionCtxt specifies the interface for getting information about the the current LRPC session. Note that the implementation is very likely to be system dependent.

Each implemenation of MessageServer type must implement the function GetSessionCtxt() that returns an object that supports this interface

func NewSessionCtxt

func NewSessionCtxt(conn net.Conn) (SessionCtxt, error)

NewSessionCtxt creates a session context object

type SessionCtxtBase

type SessionCtxtBase struct {
	// contains filtered or unexported fields
}

SessionCtxtBase manages application-dependent session information that is shared by command handlers processing messages in the same LRPC session.

Examples of such information are:

- HTTP client handle

- session context/state information

func (*SessionCtxtBase) Get

func (s *SessionCtxtBase) Get(key string) interface{}

Get returns the object that was previous saved in the session context

func (*SessionCtxtBase) Set

func (s *SessionCtxtBase) Set(key string, val interface{})

Set stores an object in the session context so that it can be retrieved later by Get()

type SessionServer

type SessionServer interface {
	// note: the following 4 functions are pre-implemented in the base struct baseSvc
	RegisterMsgsByID(map[uint16]interface{}) error
	RegisterMsgByID(uint16, interface{}) error

	Start() error
	Stop() error
	Wait() error
}

SessionServer specifies the methods that a session server must implement.

func NewLrpc2SessionServer

func NewLrpc2SessionServer(endpoint string, acl AccessControlLevel) (SessionServer, error)

NewLrpc2SessionServer creates a session server for the specific endpoint.

Input parameters:
endpoint:	The endpoint that the session server serves.
acl:		The ACL data for create sesison server

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL