proxy

package
v1.26.2 Latest Latest
Warning

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

Go to latest
Published: Dec 15, 2023 License: Apache-2.0 Imports: 10 Imported by: 0

README

The Sansshell Proxy

Overview

The Sansshell proxy is a standalone, optional component from the Sanshell server, which can be deployed to act as an intermediary between clients and Sansshell instances.

It is designed to enable larger deployments of Sansshell by providing:

  • a 'gateway' which can bridge connectivity to Sanshell instances to which clients may not have network visibility.
  • a centralized 'choke point' for logging and enforcement of fine-grained policy that may need to change rapidly.
  • a mechanism for fanning-out identical requests from a single client to multiple Sansshell instances, to simplify and accelerate 'batch' operations.

Like Sansshell itself, it is a policy-enforcing gRPC server written in Go.

Design Goals

The design of the proxy is influenced by the design of Sansshell itself, and in particular, the ability to extend Sansshell functionality via the implementation of 'service modules' that provide new capabilities.

Sansshell service modules provides a multitude of different capabilities that are often best served by a particular calling pattern. For example, a service module for executing remote commands might naturally fit into a unary call/response pattern, while a module for reading (potentially large) remote files might instead be modeled as a stream of data chunks.

Rather than constrain modules to a single pattern in order to fit the needs of the proxy, it is the proxy itself that endeavors to be flexible enough to accomodate any potentially calling pattern supported by gRPC (unary, client stream, server stream, or fully bi-directional streams).

Competing with this desire is the need to support both inspection of the payload, (for policy enforcement and logging) and the ability to 'fan out' requests to multiple backend sansshell instances.

Implementation Notes

The need to support arbitrary gRPC calling conventions yields a client/proxy protocol which effectively mimics many of the concepts in gRPC itself.

Since at least some portion of the requests served by the proxy will be streaming (client, server, or both), the client -> proxy connection must itself be a bidirectional stream.

Further, since we want to be able to fan out requests from a single client to potentially many different servers, we need to provide the ability to multiplex the data returned by multiple different instances over the same connection.

At a high-level, this produces a protocol which somewhat resembles 'gRPC over gRPC', with the client->proxy gRPC connection taking on a similar role as HTTP/2 does in gRPC itself.

The cost of this flexibility for clients is a somewhat complex implementation, requiring a proxy-aware client, and a proxy implementation that is capable of managing the state of many connections.

After establishing a connection to the proxy, clients must explicitly request the establishment of a 'stream' to each intended target using a 'StartStream' request, which specifies the intended target and method they wish to call.

Assuming the client has permission (via the policy) to establish the requested connection, and the target is available, the proxy establishes the connection to the target, and returns to the client a unique 'stream identifier' which must be used for future calls. (Note that this stream identifier is only meanginful in the context of a specific client->proxy connection, and cannot be used by other clients).

Using the stream id, the clients then send one or more StreamData requests to the proxy, which in turn sends them to the Sansshell instances that correspond to the stream ID. Fan-out is provided by the ability to specify multiple stream IDs in each StreamData request, which causes the request to be delivered to each of the specified streams.

Clients read from the proxy stream to obtain any responses (whether data or errors), each of which is identified by a previously returned stream ID.

Finished streams will cause the delivery of an inband "ServerClose" response containing the final status of the target stream (again, identified by its StreamID).

On the proxy, all of the various streams initiated by a clients are 'children' of the single client -> proxy stream, and will be cancelled automatically if the client -> proxy stream is itself broken or times out.

The 'low-level' nature of the proxy protocol is helpful for accommodating multiple gRPC call patterns, but can be somewhat difficult to use as a result. To help manage the complexity of using the proxy, sansshell also includes a grpc plugin for generating 'helper' logic for calling a particular service via the proxy.

This plugin generates new methods on each client stub with a 'OneMany' suffix (e.g. ReadOneMany for Read) that allow invoking the method (via the proxy) against multiple backend targets. Since the OneMany versions also work for single targets (even if no proxy is used), the Sansshell client code uses the OnMany version of all calls unconditionally.

sequenceDiagram
    client->>proxy: grpc.Dial / Proxy.Proxy
    client->>proxy: StartStream{target:foo, method:bar, nonce:1}
    proxy->>proxy: policy check / log
    proxy->>foo: grpc.Dial(foo)
    proxy->>client: StartStreamReply{nonce: 1, StreamID: 2}
    client->>proxy: StreamData{StreamID: 2, data:<Any[Request]>}
    proxy->>proxy: PolicyCheck / log
    proxy->>foo: Request
    foo->>proxy: Response
    proxy->>client: StreamData{StreamID: 2, data:<Any[Response]>}
    foo->>proxy: Close(status)
    proxy->>client: ServerClose{StreamID: 2, Status: status}

Documentation

Overview

Package proxy defines the RPC interface for the sansshell proxy server.

Index

Constants

This section is empty.

Variables

View Source
var File_proxy_proto protoreflect.FileDescriptor
View Source
var Proxy_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "Proxy.Proxy",
	HandlerType: (*ProxyServer)(nil),
	Methods:     []grpc.MethodDesc{},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "Proxy",
			Handler:       _Proxy_Proxy_Handler,
			ServerStreams: true,
			ClientStreams: true,
		},
	},
	Metadata: "proxy.proto",
}

Proxy_ServiceDesc is the grpc.ServiceDesc for Proxy service. It's only intended for direct use with grpc.RegisterService, and not to be introspected or modified (even as a copy)

Functions

func RegisterProxyServer

func RegisterProxyServer(s grpc.ServiceRegistrar, srv ProxyServer)

Types

type ClientCancel

type ClientCancel struct {

	// The server-assigned stream id(s) to cancel.
	StreamIds []uint64 `protobuf:"varint,1,rep,packed,name=stream_ids,json=streamIds,proto3" json:"stream_ids,omitempty"`
	// contains filtered or unexported fields
}

ClientCancel is sent by the proxy client to request cancellation of the given stream(s).

func (*ClientCancel) Descriptor deprecated

func (*ClientCancel) Descriptor() ([]byte, []int)

Deprecated: Use ClientCancel.ProtoReflect.Descriptor instead.

func (*ClientCancel) GetStreamIds

func (x *ClientCancel) GetStreamIds() []uint64

func (*ClientCancel) ProtoMessage

func (*ClientCancel) ProtoMessage()

func (*ClientCancel) ProtoReflect

func (x *ClientCancel) ProtoReflect() protoreflect.Message

func (*ClientCancel) Reset

func (x *ClientCancel) Reset()

func (*ClientCancel) String

func (x *ClientCancel) String() string

type ClientClose

type ClientClose struct {

	// The server-asssigned stream id(s) to close.
	StreamIds []uint64 `protobuf:"varint,1,rep,packed,name=stream_ids,json=streamIds,proto3" json:"stream_ids,omitempty"`
	// contains filtered or unexported fields
}

ClientClose is sent by the proxy client to indicate that no more messages will be sent to the given stream(s) Note that clients do not need to send a ClientClose for streams where client_streams is false.

func (*ClientClose) Descriptor deprecated

func (*ClientClose) Descriptor() ([]byte, []int)

Deprecated: Use ClientClose.ProtoReflect.Descriptor instead.

func (*ClientClose) GetStreamIds

func (x *ClientClose) GetStreamIds() []uint64

func (*ClientClose) ProtoMessage

func (*ClientClose) ProtoMessage()

func (*ClientClose) ProtoReflect

func (x *ClientClose) ProtoReflect() protoreflect.Message

func (*ClientClose) Reset

func (x *ClientClose) Reset()

func (*ClientClose) String

func (x *ClientClose) String() string

type ProxyClient

type ProxyClient interface {
	// Proxy represents a bidirectional stream of requests from a sanshell
	// client to one or more target sanshell instances reachable from the
	// proxy server.
	Proxy(ctx context.Context, opts ...grpc.CallOption) (Proxy_ProxyClient, error)
}

ProxyClient is the client API for Proxy service.

For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.

func NewProxyClient

func NewProxyClient(cc grpc.ClientConnInterface) ProxyClient

type ProxyReply

type ProxyReply struct {

	// Types that are assignable to Reply:
	//
	//	*ProxyReply_StartStreamReply
	//	*ProxyReply_StreamData
	//	*ProxyReply_ServerClose
	Reply isProxyReply_Reply `protobuf_oneof:"reply"`
	// contains filtered or unexported fields
}

func (*ProxyReply) Descriptor deprecated

func (*ProxyReply) Descriptor() ([]byte, []int)

Deprecated: Use ProxyReply.ProtoReflect.Descriptor instead.

func (*ProxyReply) GetReply

func (m *ProxyReply) GetReply() isProxyReply_Reply

func (*ProxyReply) GetServerClose

func (x *ProxyReply) GetServerClose() *ServerClose

func (*ProxyReply) GetStartStreamReply

func (x *ProxyReply) GetStartStreamReply() *StartStreamReply

func (*ProxyReply) GetStreamData

func (x *ProxyReply) GetStreamData() *StreamData

func (*ProxyReply) ProtoMessage

func (*ProxyReply) ProtoMessage()

func (*ProxyReply) ProtoReflect

func (x *ProxyReply) ProtoReflect() protoreflect.Message

func (*ProxyReply) Reset

func (x *ProxyReply) Reset()

func (*ProxyReply) String

func (x *ProxyReply) String() string

type ProxyReply_ServerClose

type ProxyReply_ServerClose struct {
	// An end of stream message for one or more established streams.
	ServerClose *ServerClose `protobuf:"bytes,3,opt,name=server_close,json=serverClose,proto3,oneof"`
}

type ProxyReply_StartStreamReply

type ProxyReply_StartStreamReply struct {
	// A reply to a request to initiate a stream.
	StartStreamReply *StartStreamReply `protobuf:"bytes,1,opt,name=start_stream_reply,json=startStreamReply,proto3,oneof"`
}

type ProxyReply_StreamData

type ProxyReply_StreamData struct {
	// Additional server data for one or more established streams.
	StreamData *StreamData `protobuf:"bytes,2,opt,name=stream_data,json=streamData,proto3,oneof"`
}

type ProxyRequest

type ProxyRequest struct {

	// Types that are assignable to Request:
	//
	//	*ProxyRequest_StartStream
	//	*ProxyRequest_StreamData
	//	*ProxyRequest_ClientClose
	//	*ProxyRequest_ClientCancel
	Request isProxyRequest_Request `protobuf_oneof:"request"`
	// contains filtered or unexported fields
}

func (*ProxyRequest) Descriptor deprecated

func (*ProxyRequest) Descriptor() ([]byte, []int)

Deprecated: Use ProxyRequest.ProtoReflect.Descriptor instead.

func (*ProxyRequest) GetClientCancel

func (x *ProxyRequest) GetClientCancel() *ClientCancel

func (*ProxyRequest) GetClientClose

func (x *ProxyRequest) GetClientClose() *ClientClose

func (*ProxyRequest) GetRequest

func (m *ProxyRequest) GetRequest() isProxyRequest_Request

func (*ProxyRequest) GetStartStream

func (x *ProxyRequest) GetStartStream() *StartStream

func (*ProxyRequest) GetStreamData

func (x *ProxyRequest) GetStreamData() *StreamData

func (*ProxyRequest) ProtoMessage

func (*ProxyRequest) ProtoMessage()

func (*ProxyRequest) ProtoReflect

func (x *ProxyRequest) ProtoReflect() protoreflect.Message

func (*ProxyRequest) Reset

func (x *ProxyRequest) Reset()

func (*ProxyRequest) String

func (x *ProxyRequest) String() string

type ProxyRequest_ClientCancel

type ProxyRequest_ClientCancel struct {
	// A ClientCancel indicates that the client wants to cancel
	// one or more established streams.
	ClientCancel *ClientCancel `protobuf:"bytes,4,opt,name=client_cancel,json=clientCancel,proto3,oneof"`
}

type ProxyRequest_ClientClose

type ProxyRequest_ClientClose struct {
	// A ClientClose indicates that no more data will be sent
	// for one or more established streams.
	ClientClose *ClientClose `protobuf:"bytes,3,opt,name=client_close,json=clientClose,proto3,oneof"`
}

type ProxyRequest_StartStream

type ProxyRequest_StartStream struct {
	// A request to open a new stream to a target.
	StartStream *StartStream `protobuf:"bytes,1,opt,name=start_stream,json=startStream,proto3,oneof"`
}

type ProxyRequest_StreamData

type ProxyRequest_StreamData struct {
	// Additional client data for one or more established streams.
	StreamData *StreamData `protobuf:"bytes,2,opt,name=stream_data,json=streamData,proto3,oneof"`
}

type ProxyServer

type ProxyServer interface {
	// Proxy represents a bidirectional stream of requests from a sanshell
	// client to one or more target sanshell instances reachable from the
	// proxy server.
	Proxy(Proxy_ProxyServer) error
}

ProxyServer is the server API for Proxy service. All implementations should embed UnimplementedProxyServer for forward compatibility

type Proxy_ProxyClient

type Proxy_ProxyClient interface {
	Send(*ProxyRequest) error
	Recv() (*ProxyReply, error)
	grpc.ClientStream
}

type Proxy_ProxyServer

type Proxy_ProxyServer interface {
	Send(*ProxyReply) error
	Recv() (*ProxyRequest, error)
	grpc.ServerStream
}

type ServerClose

type ServerClose struct {

	// The stream identifier, as returned in StartStreamReply
	// This can be repeated, to indicate that the same status is
	// applicable to multiple streams.
	StreamIds []uint64 `protobuf:"varint,1,rep,packed,name=stream_ids,json=streamIds,proto3" json:"stream_ids,omitempty"`
	// The final status of the stream.
	Status *Status `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
	// contains filtered or unexported fields
}

A server end-of-stream response, containing the final status of the stream.

func (*ServerClose) Descriptor deprecated

func (*ServerClose) Descriptor() ([]byte, []int)

Deprecated: Use ServerClose.ProtoReflect.Descriptor instead.

func (*ServerClose) GetStatus

func (x *ServerClose) GetStatus() *Status

func (*ServerClose) GetStreamIds

func (x *ServerClose) GetStreamIds() []uint64

func (*ServerClose) ProtoMessage

func (*ServerClose) ProtoMessage()

func (*ServerClose) ProtoReflect

func (x *ServerClose) ProtoReflect() protoreflect.Message

func (*ServerClose) Reset

func (x *ServerClose) Reset()

func (*ServerClose) String

func (x *ServerClose) String() string

type StartStream

type StartStream struct {

	// The stream target, as accepted by grpc.Dial.
	Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"`
	// The fully-qualified method name (e.g. "/Package.Service/Method")
	MethodName string `protobuf:"bytes,2,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"`
	// A nonce value which will be echoed in the reply
	// to allow the client to correlate this stream
	// request with the server-assigned stream ID.
	Nonce uint32 `protobuf:"varint,3,opt,name=nonce,proto3" json:"nonce,omitempty"`
	// A dial timeout will bound how long to wait to connect
	// to the remote target. By default in grpc for Go for instance
	// this is 20s and handled asychronously when creating the actual
	// connection. Setting a timeout here will block the Dial() call
	// to that timeout before proceeding. Each Dial() happens in their
	// own routine so this will not block the overall progress for a stream.
	DialTimeout *durationpb.Duration `protobuf:"bytes,4,opt,name=dial_timeout,json=dialTimeout,proto3" json:"dial_timeout,omitempty"`
	// contains filtered or unexported fields
}

A request to start a stream to a target host. The supplied `nonce` is an arbitrary client-chosen value that will be echoed in the returned reply to allow clients to correlate this request with the associated stream id.

func (*StartStream) Descriptor deprecated

func (*StartStream) Descriptor() ([]byte, []int)

Deprecated: Use StartStream.ProtoReflect.Descriptor instead.

func (*StartStream) GetDialTimeout added in v1.6.0

func (x *StartStream) GetDialTimeout() *durationpb.Duration

func (*StartStream) GetMethodName

func (x *StartStream) GetMethodName() string

func (*StartStream) GetNonce

func (x *StartStream) GetNonce() uint32

func (*StartStream) GetTarget

func (x *StartStream) GetTarget() string

func (*StartStream) ProtoMessage

func (*StartStream) ProtoMessage()

func (*StartStream) ProtoReflect

func (x *StartStream) ProtoReflect() protoreflect.Message

func (*StartStream) Reset

func (x *StartStream) Reset()

func (*StartStream) String

func (x *StartStream) String() string

type StartStreamReply

type StartStreamReply struct {

	// The target string originally supplied to StartStream
	Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"`
	// The nonce value supplied by the client in StartStream.
	Nonce uint32 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
	// Types that are assignable to Reply:
	//
	//	*StartStreamReply_StreamId
	//	*StartStreamReply_ErrorStatus
	Reply isStartStreamReply_Reply `protobuf_oneof:"reply"`
	// contains filtered or unexported fields
}

func (*StartStreamReply) Descriptor deprecated

func (*StartStreamReply) Descriptor() ([]byte, []int)

Deprecated: Use StartStreamReply.ProtoReflect.Descriptor instead.

func (*StartStreamReply) GetErrorStatus

func (x *StartStreamReply) GetErrorStatus() *Status

func (*StartStreamReply) GetNonce

func (x *StartStreamReply) GetNonce() uint32

func (*StartStreamReply) GetReply

func (m *StartStreamReply) GetReply() isStartStreamReply_Reply

func (*StartStreamReply) GetStreamId

func (x *StartStreamReply) GetStreamId() uint64

func (*StartStreamReply) GetTarget

func (x *StartStreamReply) GetTarget() string

func (*StartStreamReply) ProtoMessage

func (*StartStreamReply) ProtoMessage()

func (*StartStreamReply) ProtoReflect

func (x *StartStreamReply) ProtoReflect() protoreflect.Message

func (*StartStreamReply) Reset

func (x *StartStreamReply) Reset()

func (*StartStreamReply) String

func (x *StartStreamReply) String() string

type StartStreamReply_ErrorStatus

type StartStreamReply_ErrorStatus struct {
	// Status carries an error if the stream could not be
	// established.
	ErrorStatus *Status `protobuf:"bytes,4,opt,name=error_status,json=errorStatus,proto3,oneof"`
}

type StartStreamReply_StreamId

type StartStreamReply_StreamId struct {
	// The server-assigned stream identifier, which should be included
	// in all future messages for this stream.
	// stream_ids are only guaranteed to be unique within the
	// context of a single proxy stream.
	StreamId uint64 `protobuf:"varint,3,opt,name=stream_id,json=streamId,proto3,oneof"`
}

type Status

type Status struct {

	// The status code (one of google.rpc.Code)
	Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
	// A developer-targeted error message.
	Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	// List of messages carrying error details.
	Details []*anypb.Any `protobuf:"bytes,3,rep,name=details,proto3" json:"details,omitempty"`
	// contains filtered or unexported fields
}

A wire-compatible version of google.rpc.Status

func (*Status) Descriptor deprecated

func (*Status) Descriptor() ([]byte, []int)

Deprecated: Use Status.ProtoReflect.Descriptor instead.

func (*Status) GetCode

func (x *Status) GetCode() int32

func (*Status) GetDetails

func (x *Status) GetDetails() []*anypb.Any

func (*Status) GetMessage

func (x *Status) GetMessage() string

func (*Status) ProtoMessage

func (*Status) ProtoMessage()

func (*Status) ProtoReflect

func (x *Status) ProtoReflect() protoreflect.Message

func (*Status) Reset

func (x *Status) Reset()

func (*Status) String

func (x *Status) String() string

type StreamData

type StreamData struct {

	// The stream identifier, as returned in StartStreamReply
	// This can be repeated, to indicate that the same data is relevant
	// to multiple established streams.
	StreamIds []uint64 `protobuf:"varint,1,rep,packed,name=stream_ids,json=streamIds,proto3" json:"stream_ids,omitempty"`
	// The message payload
	Payload *anypb.Any `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
	// contains filtered or unexported fields
}

StreamData is used by both clients and servers to transmit data for an established stream.

func (*StreamData) Descriptor deprecated

func (*StreamData) Descriptor() ([]byte, []int)

Deprecated: Use StreamData.ProtoReflect.Descriptor instead.

func (*StreamData) GetPayload

func (x *StreamData) GetPayload() *anypb.Any

func (*StreamData) GetStreamIds

func (x *StreamData) GetStreamIds() []uint64

func (*StreamData) ProtoMessage

func (*StreamData) ProtoMessage()

func (*StreamData) ProtoReflect

func (x *StreamData) ProtoReflect() protoreflect.Message

func (*StreamData) Reset

func (x *StreamData) Reset()

func (*StreamData) String

func (x *StreamData) String() string

type UnimplementedProxyServer

type UnimplementedProxyServer struct {
}

UnimplementedProxyServer should be embedded to have forward compatible implementations.

func (UnimplementedProxyServer) Proxy

type UnsafeProxyServer

type UnsafeProxyServer interface {
	// contains filtered or unexported methods
}

UnsafeProxyServer may be embedded to opt out of forward compatibility for this service. Use of this interface is not recommended, as added methods to ProxyServer will result in compilation errors.

Directories

Path Synopsis
Package proxy provides the client side API for working with a proxy server.
Package proxy provides the client side API for working with a proxy server.
Package server provides the server-side implementation of the sansshell proxy server.
Package server provides the server-side implementation of the sansshell proxy server.
Package testutil contains helpers and utilities for writing unittests against the sansshell proxy.
Package testutil contains helpers and utilities for writing unittests against the sansshell proxy.

Jump to

Keyboard shortcuts

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