libplugin

This commit is contained in:
Boshi Lian 2022-07-02 11:37:38 +00:00
parent b49669bdae
commit dbef31dd3d
18 changed files with 5250 additions and 4 deletions

3
libplugin/doc.go Normal file
View file

@ -0,0 +1,3 @@
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative plugin.proto
package libplugin

48
libplugin/ioconn/cmd.go Normal file
View file

@ -0,0 +1,48 @@
package ioconn
import (
"io"
"net"
"os/exec"
)
type cmdconn struct {
conn
cmd *exec.Cmd
}
func (c *cmdconn) Close() error {
err := c.conn.Close()
if c.cmd.Process != nil {
return c.cmd.Process.Kill()
}
return err
}
func DialCmd(cmd *exec.Cmd) (net.Conn, io.ReadCloser, error) {
in, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
out, err := cmd.StdinPipe()
if err != nil {
return nil, nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, nil, err
}
if err := cmd.Start(); err != nil {
return nil, nil, err
}
return &cmdconn{
conn: *dial(in, out),
cmd: cmd,
}, stderr, nil
}

113
libplugin/ioconn/conn.go Normal file
View file

@ -0,0 +1,113 @@
package ioconn
import (
"fmt"
"io"
"net"
"time"
)
type addr string
func (a addr) Network() string {
return "ioconn"
}
func (a addr) String() string {
return string(a)
}
type conn struct {
in io.ReadCloser
out io.WriteCloser
}
func Dial(in io.ReadCloser, out io.WriteCloser) (net.Conn, error) {
return dial(in, out), nil
}
func dial(in io.ReadCloser, out io.WriteCloser) *conn {
return &conn{in, out}
}
// Read reads data from the connection.
// Read can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetReadDeadline.
func (c *conn) Read(b []byte) (n int, err error) {
return c.in.Read(b)
}
// Write writes data to the connection.
// Write can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetWriteDeadline.
func (c *conn) Write(b []byte) (n int, err error) {
return c.out.Write(b)
}
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
func (c *conn) Close() error {
inerr := c.in.Close()
outerr := c.out.Close()
if inerr == nil {
return outerr
}
if outerr == nil {
return outerr
}
return fmt.Errorf("io close error in: %v, out: %v", inerr, outerr)
}
// LocalAddr returns the local network address, if known.
func (c *conn) LocalAddr() net.Addr {
return addr("ioconn:local")
}
// RemoteAddr returns the remote network address, if known.
func (c *conn) RemoteAddr() net.Addr {
return addr("ioconn:remote")
}
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail instead of blocking. The deadline applies to all future
// and pending I/O, not just the immediately following call to
// Read or Write. After a deadline has been exceeded, the
// connection can be refreshed by setting a deadline in the future.
//
// If the deadline is exceeded a call to Read or Write or to other
// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
// The error's Timeout method will return true, but note that there
// are other possible errors for which the Timeout method will
// return true even if the deadline has not been exceeded.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
func (c *conn) SetDeadline(t time.Time) error {
return nil
}
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
func (c *conn) SetReadDeadline(t time.Time) error {
return nil
}
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
func (c *conn) SetWriteDeadline(t time.Time) error {
return nil
}

View file

@ -0,0 +1,37 @@
package ioconn
import (
"io"
"net"
)
type singleConnListener struct {
conn
used chan int
}
// Accept implements net.Listener
func (l *singleConnListener) Accept() (net.Conn, error) {
<-l.used
return &l.conn, nil
}
// Addr implements net.Listener
func (l *singleConnListener) Addr() net.Addr {
return l.conn.LocalAddr()
}
// Close implements net.Listener
func (l *singleConnListener) Close() error {
return l.conn.Close()
}
func ListenFromSingleIO(in io.ReadCloser, out io.WriteCloser) (net.Listener, error) {
l := &singleConnListener{
conn{in, out},
make(chan int, 1),
}
l.used <- 1 // ready for accept
return l, nil
}

2746
libplugin/plugin.pb.go Normal file

File diff suppressed because it is too large Load diff

189
libplugin/plugin.proto Normal file
View file

@ -0,0 +1,189 @@
syntax = "proto3";
package libplugin;
option go_package = "github.com/tg123/sshpiper/libplugin";
message ConnMeta {
string user_name = 1;
string from_addr = 2;
string uniq_id = 3;
}
message Upstream {
string host = 1;
int32 port = 2;
string user_name = 3;
bool ignore_host_key = 4;
oneof auth {
UpstreamNoneAuth none = 100;
UpstreamPasswordAuth password = 101;
UpstreamPrivateKeyAuth private_key = 102;
UpstreamRemoteSignerAuth remote_signer = 103;
UpstreamNextPluginAuth next_plugin = 200;
}
}
message UpstreamNoneAuth {
}
message UpstreamPasswordAuth {
string password = 1;
}
message UpstreamPrivateKeyAuth {
bytes private_key = 1;
}
message UpstreamRemoteSignerAuth{
string meta = 1;
}
message UpstreamNextPluginAuth {
map<string, string> meta = 1;
}
service SshPiperPlugin {
rpc Logs(StartLogRequest) returns (stream Log) {}
rpc ListCallbacks(ListCallbackRequest) returns (ListCallbackResponse) {}
rpc NewConnection(NewConnectionRequest) returns (NewConnectionResponse) {};
rpc NextAuthMethods(NextAuthMethodsRequest) returns (NextAuthMethodsResponse) {};
rpc NoneAuth(NoneAuthRequest) returns (NoneAuthResponse) {};
rpc PasswordAuth(PasswordAuthRequest) returns (PasswordAuthResponse) {};
rpc PublicKeyAuth(PublicKeyAuthRequest) returns (PublicKeyAuthResponse) {};
rpc KeyboardInteractiveAuth(stream KeyboardInteractiveAuthMessage) returns (stream KeyboardInteractiveAuthMessage);
rpc UpstreamAuthFailureNotice(UpstreamAuthFailureNoticeRequest) returns (UpstreamAuthFailureNoticeResponse) {};
rpc Banner(BannerRequest) returns (BannerResponse) {};
rpc VerifyHostKey (VerifyHostKeyRequest) returns (VerifyHostKeyReply) {}
}
message StartLogRequest {
string uniq_id = 1;
string level = 2;
}
message Log {
string message = 1;
}
message ListCallbackRequest {
}
message ListCallbackResponse {
repeated string callbacks = 1;
}
message NewConnectionRequest {
ConnMeta meta = 1;
}
message NewConnectionResponse {
}
message NextAuthMethodsRequest {
ConnMeta meta = 1;
}
enum AuthMethod {
NONE = 0;
PASSWORD = 1;
PUBLICKEY = 2;
KEYBOARD_INTERACTIVE = 3;
}
message NextAuthMethodsResponse {
repeated AuthMethod methods = 1;
}
message NoneAuthRequest {
ConnMeta meta = 1;
}
message NoneAuthResponse {
Upstream upstream = 1;
}
message PasswordAuthRequest {
ConnMeta meta = 1;
bytes password = 2;
}
message PasswordAuthResponse {
Upstream upstream = 1;
}
message PublicKeyAuthRequest {
ConnMeta meta = 1;
bytes public_key = 2;
}
message PublicKeyAuthResponse {
Upstream upstream = 1;
}
message KeyboardInteractiveUserResponse {
repeated string answers = 1;
}
message KeyboardInteractivePromptRequest {
message Question{
string text = 1;
bool echo = 2;
}
string name = 1;
string instruction = 2;
repeated Question questions = 3;
}
message KeyboardInteractiveMetaRequest {
}
message KeyboardInteractiveMetaResponse {
ConnMeta meta = 1;
}
message KeyboardInteractiveFinishRequest {
Upstream upstream = 1;
}
message KeyboardInteractiveAuthMessage {
oneof message {
KeyboardInteractivePromptRequest prompt_request = 1;
KeyboardInteractiveUserResponse user_response = 2;
KeyboardInteractiveMetaRequest meta_request = 3;
KeyboardInteractiveMetaResponse meta_response = 4;
KeyboardInteractiveFinishRequest finish_request = 5;
}
}
message UpstreamAuthFailureNoticeRequest {
ConnMeta meta = 1;
string method = 2;
string error = 3;
repeated AuthMethod allowed_methods = 4;
}
message UpstreamAuthFailureNoticeResponse {
}
message BannerRequest {
ConnMeta meta = 1;
}
message BannerResponse {
string message = 1;
}
message VerifyHostKeyRequest {
ConnMeta meta = 1;
bytes key = 2;
}
message VerifyHostKeyReply {
bool verified = 1;
}

521
libplugin/plugin_grpc.pb.go Normal file
View file

@ -0,0 +1,521 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package libplugin
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// SshPiperPluginClient is the client API for SshPiperPlugin 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.
type SshPiperPluginClient interface {
Logs(ctx context.Context, in *StartLogRequest, opts ...grpc.CallOption) (SshPiperPlugin_LogsClient, error)
ListCallbacks(ctx context.Context, in *ListCallbackRequest, opts ...grpc.CallOption) (*ListCallbackResponse, error)
NewConnection(ctx context.Context, in *NewConnectionRequest, opts ...grpc.CallOption) (*NewConnectionResponse, error)
NextAuthMethods(ctx context.Context, in *NextAuthMethodsRequest, opts ...grpc.CallOption) (*NextAuthMethodsResponse, error)
NoneAuth(ctx context.Context, in *NoneAuthRequest, opts ...grpc.CallOption) (*NoneAuthResponse, error)
PasswordAuth(ctx context.Context, in *PasswordAuthRequest, opts ...grpc.CallOption) (*PasswordAuthResponse, error)
PublicKeyAuth(ctx context.Context, in *PublicKeyAuthRequest, opts ...grpc.CallOption) (*PublicKeyAuthResponse, error)
KeyboardInteractiveAuth(ctx context.Context, opts ...grpc.CallOption) (SshPiperPlugin_KeyboardInteractiveAuthClient, error)
UpstreamAuthFailureNotice(ctx context.Context, in *UpstreamAuthFailureNoticeRequest, opts ...grpc.CallOption) (*UpstreamAuthFailureNoticeResponse, error)
Banner(ctx context.Context, in *BannerRequest, opts ...grpc.CallOption) (*BannerResponse, error)
VerifyHostKey(ctx context.Context, in *VerifyHostKeyRequest, opts ...grpc.CallOption) (*VerifyHostKeyReply, error)
}
type sshPiperPluginClient struct {
cc grpc.ClientConnInterface
}
func NewSshPiperPluginClient(cc grpc.ClientConnInterface) SshPiperPluginClient {
return &sshPiperPluginClient{cc}
}
func (c *sshPiperPluginClient) Logs(ctx context.Context, in *StartLogRequest, opts ...grpc.CallOption) (SshPiperPlugin_LogsClient, error) {
stream, err := c.cc.NewStream(ctx, &SshPiperPlugin_ServiceDesc.Streams[0], "/libplugin.SshPiperPlugin/Logs", opts...)
if err != nil {
return nil, err
}
x := &sshPiperPluginLogsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type SshPiperPlugin_LogsClient interface {
Recv() (*Log, error)
grpc.ClientStream
}
type sshPiperPluginLogsClient struct {
grpc.ClientStream
}
func (x *sshPiperPluginLogsClient) Recv() (*Log, error) {
m := new(Log)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *sshPiperPluginClient) ListCallbacks(ctx context.Context, in *ListCallbackRequest, opts ...grpc.CallOption) (*ListCallbackResponse, error) {
out := new(ListCallbackResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/ListCallbacks", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) NewConnection(ctx context.Context, in *NewConnectionRequest, opts ...grpc.CallOption) (*NewConnectionResponse, error) {
out := new(NewConnectionResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/NewConnection", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) NextAuthMethods(ctx context.Context, in *NextAuthMethodsRequest, opts ...grpc.CallOption) (*NextAuthMethodsResponse, error) {
out := new(NextAuthMethodsResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/NextAuthMethods", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) NoneAuth(ctx context.Context, in *NoneAuthRequest, opts ...grpc.CallOption) (*NoneAuthResponse, error) {
out := new(NoneAuthResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/NoneAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) PasswordAuth(ctx context.Context, in *PasswordAuthRequest, opts ...grpc.CallOption) (*PasswordAuthResponse, error) {
out := new(PasswordAuthResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/PasswordAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) PublicKeyAuth(ctx context.Context, in *PublicKeyAuthRequest, opts ...grpc.CallOption) (*PublicKeyAuthResponse, error) {
out := new(PublicKeyAuthResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/PublicKeyAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) KeyboardInteractiveAuth(ctx context.Context, opts ...grpc.CallOption) (SshPiperPlugin_KeyboardInteractiveAuthClient, error) {
stream, err := c.cc.NewStream(ctx, &SshPiperPlugin_ServiceDesc.Streams[1], "/libplugin.SshPiperPlugin/KeyboardInteractiveAuth", opts...)
if err != nil {
return nil, err
}
x := &sshPiperPluginKeyboardInteractiveAuthClient{stream}
return x, nil
}
type SshPiperPlugin_KeyboardInteractiveAuthClient interface {
Send(*KeyboardInteractiveAuthMessage) error
Recv() (*KeyboardInteractiveAuthMessage, error)
grpc.ClientStream
}
type sshPiperPluginKeyboardInteractiveAuthClient struct {
grpc.ClientStream
}
func (x *sshPiperPluginKeyboardInteractiveAuthClient) Send(m *KeyboardInteractiveAuthMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *sshPiperPluginKeyboardInteractiveAuthClient) Recv() (*KeyboardInteractiveAuthMessage, error) {
m := new(KeyboardInteractiveAuthMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *sshPiperPluginClient) UpstreamAuthFailureNotice(ctx context.Context, in *UpstreamAuthFailureNoticeRequest, opts ...grpc.CallOption) (*UpstreamAuthFailureNoticeResponse, error) {
out := new(UpstreamAuthFailureNoticeResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/UpstreamAuthFailureNotice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) Banner(ctx context.Context, in *BannerRequest, opts ...grpc.CallOption) (*BannerResponse, error) {
out := new(BannerResponse)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/Banner", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *sshPiperPluginClient) VerifyHostKey(ctx context.Context, in *VerifyHostKeyRequest, opts ...grpc.CallOption) (*VerifyHostKeyReply, error) {
out := new(VerifyHostKeyReply)
err := c.cc.Invoke(ctx, "/libplugin.SshPiperPlugin/VerifyHostKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SshPiperPluginServer is the server API for SshPiperPlugin service.
// All implementations must embed UnimplementedSshPiperPluginServer
// for forward compatibility
type SshPiperPluginServer interface {
Logs(*StartLogRequest, SshPiperPlugin_LogsServer) error
ListCallbacks(context.Context, *ListCallbackRequest) (*ListCallbackResponse, error)
NewConnection(context.Context, *NewConnectionRequest) (*NewConnectionResponse, error)
NextAuthMethods(context.Context, *NextAuthMethodsRequest) (*NextAuthMethodsResponse, error)
NoneAuth(context.Context, *NoneAuthRequest) (*NoneAuthResponse, error)
PasswordAuth(context.Context, *PasswordAuthRequest) (*PasswordAuthResponse, error)
PublicKeyAuth(context.Context, *PublicKeyAuthRequest) (*PublicKeyAuthResponse, error)
KeyboardInteractiveAuth(SshPiperPlugin_KeyboardInteractiveAuthServer) error
UpstreamAuthFailureNotice(context.Context, *UpstreamAuthFailureNoticeRequest) (*UpstreamAuthFailureNoticeResponse, error)
Banner(context.Context, *BannerRequest) (*BannerResponse, error)
VerifyHostKey(context.Context, *VerifyHostKeyRequest) (*VerifyHostKeyReply, error)
mustEmbedUnimplementedSshPiperPluginServer()
}
// UnimplementedSshPiperPluginServer must be embedded to have forward compatible implementations.
type UnimplementedSshPiperPluginServer struct {
}
func (UnimplementedSshPiperPluginServer) Logs(*StartLogRequest, SshPiperPlugin_LogsServer) error {
return status.Errorf(codes.Unimplemented, "method Logs not implemented")
}
func (UnimplementedSshPiperPluginServer) ListCallbacks(context.Context, *ListCallbackRequest) (*ListCallbackResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCallbacks not implemented")
}
func (UnimplementedSshPiperPluginServer) NewConnection(context.Context, *NewConnectionRequest) (*NewConnectionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NewConnection not implemented")
}
func (UnimplementedSshPiperPluginServer) NextAuthMethods(context.Context, *NextAuthMethodsRequest) (*NextAuthMethodsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NextAuthMethods not implemented")
}
func (UnimplementedSshPiperPluginServer) NoneAuth(context.Context, *NoneAuthRequest) (*NoneAuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NoneAuth not implemented")
}
func (UnimplementedSshPiperPluginServer) PasswordAuth(context.Context, *PasswordAuthRequest) (*PasswordAuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PasswordAuth not implemented")
}
func (UnimplementedSshPiperPluginServer) PublicKeyAuth(context.Context, *PublicKeyAuthRequest) (*PublicKeyAuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PublicKeyAuth not implemented")
}
func (UnimplementedSshPiperPluginServer) KeyboardInteractiveAuth(SshPiperPlugin_KeyboardInteractiveAuthServer) error {
return status.Errorf(codes.Unimplemented, "method KeyboardInteractiveAuth not implemented")
}
func (UnimplementedSshPiperPluginServer) UpstreamAuthFailureNotice(context.Context, *UpstreamAuthFailureNoticeRequest) (*UpstreamAuthFailureNoticeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpstreamAuthFailureNotice not implemented")
}
func (UnimplementedSshPiperPluginServer) Banner(context.Context, *BannerRequest) (*BannerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Banner not implemented")
}
func (UnimplementedSshPiperPluginServer) VerifyHostKey(context.Context, *VerifyHostKeyRequest) (*VerifyHostKeyReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyHostKey not implemented")
}
func (UnimplementedSshPiperPluginServer) mustEmbedUnimplementedSshPiperPluginServer() {}
// UnsafeSshPiperPluginServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SshPiperPluginServer will
// result in compilation errors.
type UnsafeSshPiperPluginServer interface {
mustEmbedUnimplementedSshPiperPluginServer()
}
func RegisterSshPiperPluginServer(s grpc.ServiceRegistrar, srv SshPiperPluginServer) {
s.RegisterService(&SshPiperPlugin_ServiceDesc, srv)
}
func _SshPiperPlugin_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(StartLogRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(SshPiperPluginServer).Logs(m, &sshPiperPluginLogsServer{stream})
}
type SshPiperPlugin_LogsServer interface {
Send(*Log) error
grpc.ServerStream
}
type sshPiperPluginLogsServer struct {
grpc.ServerStream
}
func (x *sshPiperPluginLogsServer) Send(m *Log) error {
return x.ServerStream.SendMsg(m)
}
func _SshPiperPlugin_ListCallbacks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListCallbackRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).ListCallbacks(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/ListCallbacks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).ListCallbacks(ctx, req.(*ListCallbackRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_NewConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NewConnectionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).NewConnection(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/NewConnection",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).NewConnection(ctx, req.(*NewConnectionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_NextAuthMethods_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NextAuthMethodsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).NextAuthMethods(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/NextAuthMethods",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).NextAuthMethods(ctx, req.(*NextAuthMethodsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_NoneAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NoneAuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).NoneAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/NoneAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).NoneAuth(ctx, req.(*NoneAuthRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_PasswordAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PasswordAuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).PasswordAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/PasswordAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).PasswordAuth(ctx, req.(*PasswordAuthRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_PublicKeyAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PublicKeyAuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).PublicKeyAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/PublicKeyAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).PublicKeyAuth(ctx, req.(*PublicKeyAuthRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_KeyboardInteractiveAuth_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(SshPiperPluginServer).KeyboardInteractiveAuth(&sshPiperPluginKeyboardInteractiveAuthServer{stream})
}
type SshPiperPlugin_KeyboardInteractiveAuthServer interface {
Send(*KeyboardInteractiveAuthMessage) error
Recv() (*KeyboardInteractiveAuthMessage, error)
grpc.ServerStream
}
type sshPiperPluginKeyboardInteractiveAuthServer struct {
grpc.ServerStream
}
func (x *sshPiperPluginKeyboardInteractiveAuthServer) Send(m *KeyboardInteractiveAuthMessage) error {
return x.ServerStream.SendMsg(m)
}
func (x *sshPiperPluginKeyboardInteractiveAuthServer) Recv() (*KeyboardInteractiveAuthMessage, error) {
m := new(KeyboardInteractiveAuthMessage)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _SshPiperPlugin_UpstreamAuthFailureNotice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpstreamAuthFailureNoticeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).UpstreamAuthFailureNotice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/UpstreamAuthFailureNotice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).UpstreamAuthFailureNotice(ctx, req.(*UpstreamAuthFailureNoticeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_Banner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BannerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).Banner(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/Banner",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).Banner(ctx, req.(*BannerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SshPiperPlugin_VerifyHostKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyHostKeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SshPiperPluginServer).VerifyHostKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/libplugin.SshPiperPlugin/VerifyHostKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SshPiperPluginServer).VerifyHostKey(ctx, req.(*VerifyHostKeyRequest))
}
return interceptor(ctx, in, info, handler)
}
// SshPiperPlugin_ServiceDesc is the grpc.ServiceDesc for SshPiperPlugin service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var SshPiperPlugin_ServiceDesc = grpc.ServiceDesc{
ServiceName: "libplugin.SshPiperPlugin",
HandlerType: (*SshPiperPluginServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListCallbacks",
Handler: _SshPiperPlugin_ListCallbacks_Handler,
},
{
MethodName: "NewConnection",
Handler: _SshPiperPlugin_NewConnection_Handler,
},
{
MethodName: "NextAuthMethods",
Handler: _SshPiperPlugin_NextAuthMethods_Handler,
},
{
MethodName: "NoneAuth",
Handler: _SshPiperPlugin_NoneAuth_Handler,
},
{
MethodName: "PasswordAuth",
Handler: _SshPiperPlugin_PasswordAuth_Handler,
},
{
MethodName: "PublicKeyAuth",
Handler: _SshPiperPlugin_PublicKeyAuth_Handler,
},
{
MethodName: "UpstreamAuthFailureNotice",
Handler: _SshPiperPlugin_UpstreamAuthFailureNotice_Handler,
},
{
MethodName: "Banner",
Handler: _SshPiperPlugin_Banner_Handler,
},
{
MethodName: "VerifyHostKey",
Handler: _SshPiperPlugin_VerifyHostKey_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Logs",
Handler: _SshPiperPlugin_Logs_Handler,
ServerStreams: true,
},
{
StreamName: "KeyboardInteractiveAuth",
Handler: _SshPiperPlugin_KeyboardInteractiveAuth_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "plugin.proto",
}

374
libplugin/pluginbase.go Normal file
View file

@ -0,0 +1,374 @@
package libplugin
import (
"bufio"
context "context"
"fmt"
"io"
"net"
"os"
"github.com/tg123/sshpiper/libplugin/ioconn"
"google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
type ConnMetadata interface {
User() string
RemoteAddr() string
UniqueID() string
}
func (c *ConnMeta) User() string {
return c.UserName
}
func (c *ConnMeta) RemoteAddr() string {
return c.FromAddr
}
func (c *ConnMeta) UniqueID() string {
return c.UniqId
}
type KeyboardInteractiveChallenge func(instruction string, question string, echo bool) (answer string, err error)
type SshPiperPluginConfig struct {
NewConnectionCallback func(conn ConnMetadata) error
NextAuthMethodsCallback func(conn ConnMetadata) ([]string, error)
NoneAuthCallback func(conn ConnMetadata) (*Upstream, error)
PasswordCallback func(conn ConnMetadata, password []byte) (*Upstream, error)
PublicKeyCallback func(conn ConnMetadata, key []byte) (*Upstream, error)
KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Upstream, error)
UpstreamAuthFailureCallback func(conn ConnMetadata, method string, err error)
BannerCallback func(conn ConnMetadata) string
VerifyHostKeyCallback func(conn ConnMetadata, key []byte) (bool, error)
}
type SshPiperPlugin interface {
GetLoggerOutput() io.Writer
GetGrpcServer() *grpc.Server
Serve() error
}
func NewFromStdio(config SshPiperPluginConfig) (SshPiperPlugin, error) {
s := grpc.NewServer()
l, err := ioconn.ListenFromSingleIO(os.Stdin, os.Stdout)
if err != nil {
return nil, err
}
return NewFromGrpc(config, s, l)
}
func NewFromGrpc(config SshPiperPluginConfig, grpc *grpc.Server, listener net.Listener) (SshPiperPlugin, error) {
r, w := io.Pipe()
s := &server{
config: config,
grpc: grpc,
listener: listener,
logwriter: w,
logs: make(chan string, 1000),
}
go func() {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
s.logs <- scanner.Text()
}
}()
RegisterSshPiperPluginServer(s.grpc, s)
return s, nil
}
type server struct {
UnimplementedSshPiperPluginServer
config SshPiperPluginConfig
grpc *grpc.Server
listener net.Listener
logs chan string
logwriter io.Writer
}
func (s *server) GetGrpcServer() *grpc.Server {
return s.grpc
}
func (s *server) GetLoggerOutput() io.Writer {
return s.logwriter
}
func (s *server) Serve() error {
return s.grpc.Serve(s.listener)
}
func (s *server) Logs(req *StartLogRequest, stream SshPiperPlugin_LogsServer) error {
for log := range s.logs {
if err := stream.Send(&Log{
Message: log,
}); err != nil {
return err
}
}
return nil
}
func (s *server) ListCallbacks(ctx context.Context, req *ListCallbackRequest) (*ListCallbackResponse, error) {
var cb []string
if s.config.NewConnectionCallback != nil {
cb = append(cb, "NewConnection")
}
if s.config.NextAuthMethodsCallback != nil {
cb = append(cb, "NextAuthMethods")
}
if s.config.NoneAuthCallback != nil {
cb = append(cb, "NoneAuth")
}
if s.config.PasswordCallback != nil {
cb = append(cb, "PasswordAuth")
}
if s.config.PublicKeyCallback != nil {
cb = append(cb, "PublicKeyAuth")
}
if s.config.KeyboardInteractiveCallback != nil {
cb = append(cb, "KeyboardInteractiveAuth")
}
if s.config.UpstreamAuthFailureCallback != nil {
cb = append(cb, "UpstreamAuthFailure")
}
if s.config.BannerCallback != nil {
cb = append(cb, "Banner")
}
if s.config.VerifyHostKeyCallback != nil {
cb = append(cb, "VerifyHostKey")
}
return &ListCallbackResponse{
Callbacks: cb,
}, nil
}
func (s *server) NewConnection(ctx context.Context, req *NewConnectionRequest) (*NewConnectionResponse, error) {
if s.config.NewConnectionCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method NewConnection not implemented")
}
if err := s.config.NewConnectionCallback(req.Meta); err != nil {
return nil, err
}
return &NewConnectionResponse{}, nil
}
func (s *server) NextAuthMethods(ctx context.Context, req *NextAuthMethodsRequest) (*NextAuthMethodsResponse, error) {
if s.config.NextAuthMethodsCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method NextAuthMethods not implemented")
}
methods, err := s.config.NextAuthMethodsCallback(req.Meta)
if err != nil {
return nil, err
}
resp := &NextAuthMethodsResponse{}
for _, method := range methods {
m := AuthMethodFromName(method)
if m == -1 {
return nil, status.Errorf(codes.InvalidArgument, "unknown method %s", method)
}
resp.Methods = append(resp.Methods, m)
}
return resp, nil
}
func (s *server) NoneAuth(ctx context.Context, req *NoneAuthRequest) (*NoneAuthResponse, error) {
if s.config.NoneAuthCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method NoneAuth not implemented")
}
upstream, err := s.config.NoneAuthCallback(req.Meta)
if err != nil {
return nil, err
}
return &NoneAuthResponse{
Upstream: upstream,
}, nil
}
func (s *server) PasswordAuth(ctx context.Context, req *PasswordAuthRequest) (*PasswordAuthResponse, error) {
if s.config.PasswordCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method PasswordAuth not implemented")
}
upstream, err := s.config.PasswordCallback(req.Meta, req.Password)
if err != nil {
return nil, err
}
return &PasswordAuthResponse{
Upstream: upstream,
}, nil
}
func (s *server) PublicKeyAuth(ctx context.Context, req *PublicKeyAuthRequest) (*PublicKeyAuthResponse, error) {
if s.config.PublicKeyCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method PublicKeyAuth not implemented")
}
upstream, err := s.config.PublicKeyCallback(req.Meta, req.PublicKey)
if err != nil {
return nil, err
}
return &PublicKeyAuthResponse{
Upstream: upstream,
}, nil
}
func (s *server) KeyboardInteractiveAuth(stream SshPiperPlugin_KeyboardInteractiveAuthServer) error {
if s.config.KeyboardInteractiveCallback == nil {
return status.Errorf(codes.Unimplemented, "method KeyboardInteractiveAuth not implemented")
}
if err := stream.Send(&KeyboardInteractiveAuthMessage{
Message: &KeyboardInteractiveAuthMessage_MetaRequest{},
}); err != nil {
return err
}
metareply, err := stream.Recv()
if err != nil {
return err
}
meta := metareply.GetMetaResponse()
if meta == nil {
return status.Errorf(codes.InvalidArgument, "missing meta")
}
upstream, err := s.config.KeyboardInteractiveCallback(meta.Meta, func(instruction string, question string, echo bool) (answer string, err error) {
var questions []*KeyboardInteractivePromptRequest_Question
if question != "" {
questions = append(questions, &KeyboardInteractivePromptRequest_Question{
Text: question,
Echo: echo,
})
}
if err := stream.Send(&KeyboardInteractiveAuthMessage{
Message: &KeyboardInteractiveAuthMessage_PromptRequest{
PromptRequest: &KeyboardInteractivePromptRequest{
Name: "", // temporary unused
Instruction: instruction,
Questions: questions,
},
},
}); err != nil {
return "", err
}
if question == "" {
return "", nil
}
userInputReply, err := stream.Recv()
if err != nil {
return "", err
}
userInput := userInputReply.GetUserResponse()
if userInput == nil {
return "", status.Errorf(codes.InvalidArgument, "missing user input")
}
if len(userInput.Answers) != 1 {
return "", status.Errorf(codes.InvalidArgument, "expected 1 answer, got %d", len(userInput.Answers))
}
return userInput.Answers[0], nil
})
if err != nil {
return err
}
if err := stream.Send(&KeyboardInteractiveAuthMessage{
Message: &KeyboardInteractiveAuthMessage_FinishRequest{
FinishRequest: &KeyboardInteractiveFinishRequest{
Upstream: upstream,
},
},
}); err != nil {
return err
}
return nil
}
func (s *server) UpstreamAuthFailureNotice(ctx context.Context, req *UpstreamAuthFailureNoticeRequest) (*UpstreamAuthFailureNoticeResponse, error) {
if s.config.UpstreamAuthFailureCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method UpstreamAuthFailureNotice not implemented")
}
s.config.UpstreamAuthFailureCallback(req.Meta, req.Method, fmt.Errorf(req.Error))
return &UpstreamAuthFailureNoticeResponse{}, nil
}
func (s *server) Banner(ctx context.Context, req *BannerRequest) (*BannerResponse, error) {
if s.config.BannerCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method Banner not implemented")
}
msg := s.config.BannerCallback(req.Meta)
return &BannerResponse{
Message: msg,
}, nil
}
func (s *server) VerifyHostKey(ctx context.Context, req *VerifyHostKeyRequest) (*VerifyHostKeyReply, error) {
if s.config.VerifyHostKeyCallback == nil {
return nil, status.Errorf(codes.Unimplemented, "method VerifyHostKey not implemented")
}
verifed, err := s.config.VerifyHostKeyCallback(req.Meta, req.Key)
if err != nil {
return nil, err
}
return &VerifyHostKeyReply{
Verified: verifed,
}, nil
}

138
libplugin/util.go Normal file
View file

@ -0,0 +1,138 @@
package libplugin
import (
"fmt"
"io"
"net"
"os"
"strconv"
"github.com/sirupsen/logrus"
)
func WriterToFile(writer io.Writer) (*os.File, error) {
r, w, err := os.Pipe()
if err != nil {
return nil, err
}
go io.Copy(writer, r)
return w, nil
}
func AuthMethodTypeToName(a AuthMethod) string {
switch a {
case AuthMethod_NONE:
return "none"
case AuthMethod_PASSWORD:
return "password"
case AuthMethod_PUBLICKEY:
return "publickey"
case AuthMethod_KEYBOARD_INTERACTIVE:
return "keyboard-interactive"
}
return ""
}
func AuthMethodFromName(n string) AuthMethod {
switch n {
case "none":
return AuthMethod_NONE
case "password":
return AuthMethod_PASSWORD
case "publickey":
return AuthMethod_PUBLICKEY
case "keyboard-interactive":
return AuthMethod_KEYBOARD_INTERACTIVE
}
return -1
}
func ConfigStdioLogrus(p SshPiperPlugin, logger *logrus.Logger) {
if logger == nil {
logger = logrus.StandardLogger()
}
logger.SetOutput(p.GetLoggerOutput())
logger.SetFormatter(&logrus.TextFormatter{ForceColors: true})
}
// SplitHostPortForSSH is the modified version of net.SplitHostPort but return port 22 is no port is specified
func SplitHostPortForSSH(addr string) (host string, port int, err error) {
host = addr
h, p, err := net.SplitHostPort(host)
if err == nil {
host = h
port, err = strconv.Atoi(p)
if err != nil {
return
}
} else if host != "" {
// test valid after concat :22
if _, _, err = net.SplitHostPort(host + ":22"); err == nil {
port = 22
}
}
if host == "" {
err = fmt.Errorf("empty addr")
}
return
}
// DialForSSH is the modified version of net.Dial, would add ":22" automaticlly
func DialForSSH(addr string) (net.Conn, error) {
if _, _, err := net.SplitHostPort(addr); err != nil && addr != "" {
// test valid after concat :22
if _, _, err := net.SplitHostPort(addr + ":22"); err == nil {
addr += ":22"
}
}
return net.Dial("tcp", addr)
}
func CreateNoneAuth(password []byte) *Upstream_None {
return &Upstream_None{
None: &UpstreamNoneAuth{},
}
}
func CreatePasswordAuth(password []byte) *Upstream_Password {
return CreatePasswordAuthFromString(string(password))
}
func CreatePasswordAuthFromString(password string) *Upstream_Password {
return &Upstream_Password{
Password: &UpstreamPasswordAuth{
Password: password,
},
}
}
func CreatePrivateKeyAuth(key []byte) *Upstream_PrivateKey {
return &Upstream_PrivateKey{
PrivateKey: &UpstreamPrivateKeyAuth{
PrivateKey: key,
},
}
}
func CreateRemoteSignerAuth(meta string) *Upstream_RemoteSigner {
return &Upstream_RemoteSigner{
RemoteSigner: &UpstreamRemoteSignerAuth{
Meta: meta,
},
}
}
func CreateNextPluginAuth(meta map[string]string) *Upstream_NextPlugin {
return &Upstream_NextPlugin{
NextPlugin: &UpstreamNextPluginAuth{
Meta: meta,
},
}
}