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

135
cmd/sshpiperd/daemon.go Normal file
View file

@ -0,0 +1,135 @@
package main
import (
"fmt"
"io/ioutil"
"net"
"path/filepath"
"time"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/cmd/sshpiperd/internal/plugin"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/ssh"
)
type daemon struct {
config *ssh.PiperConfig
lis net.Listener
loginGraceTime time.Duration
}
func newDaemon(ctx *cli.Context) (*daemon, error) {
config := &ssh.PiperConfig{}
config.SetDefaults()
privateKeys, err := filepath.Glob(ctx.String("server-key"))
if err != nil {
return nil, err
}
if len(privateKeys) == 0 {
return nil, fmt.Errorf("no server key found")
}
log.Infof("found host keys %v", privateKeys)
for _, privateKey := range privateKeys {
log.Infof("loading host key %v", privateKey)
privateBytes, err := ioutil.ReadFile(privateKey)
if err != nil {
return nil, err
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return nil, err
}
config.AddHostKey(private)
}
lis, err := net.Listen("tcp", net.JoinHostPort(ctx.String("address"), ctx.String("port")))
if err != nil {
return nil, fmt.Errorf("failed to listen for connection: %v", err)
}
return &daemon{
config: config,
lis: lis,
loginGraceTime: ctx.Duration("login-grace-time"),
}, nil
}
func (d *daemon) install(plugins ...*plugin.GrpcPlugin) error {
if len(plugins) == 0 {
return fmt.Errorf("no plugins found")
}
// if len(plugins) == 1 {
// return plugins[0].InstallPiperConfig(d.config)
// }
m := plugin.ChainPlugins{}
for _, p := range plugins {
if err := m.Append(p); err != nil {
return err
}
}
return m.InstallPiperConfig(d.config)
}
func (d *daemon) run() error {
defer d.lis.Close()
log.Infof("sshpiperd is listening on: %v", d.lis.Addr().String())
for {
conn, err := d.lis.Accept()
if err != nil {
log.Debugf("failed to accept connection: %v", err)
continue
}
log.Debugf("connection accepted: %v", conn.RemoteAddr())
go func(c net.Conn) {
defer c.Close()
pipec := make(chan *ssh.PiperConn)
errorc := make(chan error)
go func() {
p, err := ssh.NewSSHPiperConn(c, d.config)
if err != nil {
errorc <- err
return
}
pipec <- p
}()
var p *ssh.PiperConn
select {
case p = <-pipec:
case err := <-errorc:
log.Debugf("connection from %v establishing failed reason: %v", c.RemoteAddr(), err)
return
case <-time.After(d.loginGraceTime):
log.Debugf("pipe establishing timeout, disconnected connection from %v", c.RemoteAddr())
return
}
defer p.Close()
log.Infof("ssh connection pipe created %v -> %v", p.DownstreamConnMeta().RemoteAddr(), p.UpstreamConnMeta().RemoteAddr().String())
// TODO add screen recording
err = p.Wait()
log.Infof("connection from %v closed reason: %v", c.RemoteAddr(), err)
}(conn)
}
}

View file

@ -0,0 +1,144 @@
package plugin
import (
"fmt"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/libplugin"
"golang.org/x/crypto/ssh"
)
type ChainPlugins struct {
pluginsCallback []*ssh.PiperConfig
plugins []*GrpcPlugin
}
func (cp *ChainPlugins) Append(p *GrpcPlugin) error {
config, err := p.CreatePiperConfig()
if err != nil {
return err
}
p.OnNextPlugin = cp.onNextPlugin
cp.pluginsCallback = append(cp.pluginsCallback, config)
cp.plugins = append(cp.plugins, p)
return nil
}
func (cp *ChainPlugins) onNextPlugin(challengeCtx ssh.ChallengeContext, upstream *libplugin.UpstreamNextPluginAuth) error {
chain := challengeCtx.(*chainConnMeta)
if chain.current+1 >= len(cp.pluginsCallback) {
return fmt.Errorf("no more plugins")
}
chain.current++
return nil
}
type chainConnMeta struct {
connMeta
current int
}
func (cp *ChainPlugins) CreateChallengeContext(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
uiq, err := uuid.NewRandom()
if err != nil {
return nil, err
}
meta := chainConnMeta{
connMeta: connMeta{
UserName: conn.User(),
FromAddr: conn.RemoteAddr().String(),
UniqId: uiq.String(),
},
}
for _, p := range cp.plugins {
if err := p.NewConnection(&meta.connMeta); err != nil {
return nil, err
}
}
return &meta, nil
}
func (cp *ChainPlugins) NextAuthMethods(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) {
chain := challengeCtx.(*chainConnMeta)
config := cp.pluginsCallback[chain.current]
if config.NextAuthMethods != nil {
return config.NextAuthMethods(conn, challengeCtx)
}
var methods []string
if config.NoneAuthCallback != nil {
methods = append(methods, "none")
}
if config.PasswordCallback != nil {
methods = append(methods, "password")
}
if config.PublicKeyCallback != nil {
methods = append(methods, "publickey")
}
if config.KeyboardInteractiveCallback != nil {
methods = append(methods, "keyboard-interactive")
}
log.Debugf("next auth methods %v", methods)
return methods, nil
}
func (cp *ChainPlugins) InstallPiperConfig(config *ssh.PiperConfig) error {
config.CreateChallengeContext = func(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
ctx, err := cp.CreateChallengeContext(conn)
if err != nil {
log.Errorf("cannot create challenge context %v", err)
}
return ctx, err
}
config.NextAuthMethods = cp.NextAuthMethods
config.NoneAuthCallback = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
return cp.pluginsCallback[challengeCtx.(*chainConnMeta).current].NoneAuthCallback(conn, challengeCtx)
}
config.PasswordCallback = func(conn ssh.ConnMetadata, password []byte, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
return cp.pluginsCallback[challengeCtx.(*chainConnMeta).current].PasswordCallback(conn, password, challengeCtx)
}
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
return cp.pluginsCallback[challengeCtx.(*chainConnMeta).current].PublicKeyCallback(conn, key, challengeCtx)
}
config.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
return cp.pluginsCallback[challengeCtx.(*chainConnMeta).current].KeyboardInteractiveCallback(conn, client, challengeCtx)
}
config.UpstreamAuthFailureCallback = func(conn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) {
cur := cp.pluginsCallback[challengeCtx.(*chainConnMeta).current]
if cur.UpstreamAuthFailureCallback != nil {
cur.UpstreamAuthFailureCallback(conn, method, err, challengeCtx)
}
}
config.BannerCallback = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
cur := cp.pluginsCallback[challengeCtx.(*chainConnMeta).current]
if cur.BannerCallback != nil {
return cur.BannerCallback(conn, challengeCtx)
}
return ""
}
return nil
}

View file

@ -0,0 +1,519 @@
package plugin
import (
"context"
"fmt"
"io"
"net"
"os/exec"
"strconv"
"time"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/tg123/remotesigner"
"github.com/tg123/remotesigner/grpcsigner"
"github.com/tg123/sshpiper/libplugin"
"github.com/tg123/sshpiper/libplugin/ioconn"
"golang.org/x/crypto/ssh"
"google.golang.org/grpc"
)
type GrpcPlugin struct {
OnNextPlugin func(conn ssh.ChallengeContext, upstream *libplugin.UpstreamNextPluginAuth) error
grpcconn *grpc.ClientConn
client libplugin.SshPiperPluginClient
remotesignerClient grpcsigner.SignerClient
hasNewConnectionCallback bool
allowedMethod map[string]bool
}
func DialGrpc(conn *grpc.ClientConn) (*GrpcPlugin, error) {
p := &GrpcPlugin{
grpcconn: conn,
client: libplugin.NewSshPiperPluginClient(conn),
remotesignerClient: grpcsigner.NewSignerClient(conn),
}
return p, nil
}
func (g *GrpcPlugin) InstallPiperConfig(config *ssh.PiperConfig) error {
cb, err := g.client.ListCallbacks(context.Background(), &libplugin.ListCallbackRequest{})
if err != nil {
return err
}
// config.NextAuthMethods = g.NextAuthMethodsLocal
// config.UpstreamAuthFailureCallback = g.UpstreamAuthFailureCallbackLocal
config.CreateChallengeContext = func(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
ctx, err := g.CreateChallengeContext(conn)
if err != nil {
log.Errorf("cannot create challenge context %v", err)
}
return ctx, err
}
for _, c := range cb.Callbacks {
switch c {
case "NewConnection":
g.hasNewConnectionCallback = true
case "NextAuthMethods":
config.NextAuthMethods = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) {
methods, err := g.NextAuthMethodsRemote(conn, challengeCtx)
if err != nil {
log.Errorf("cannot get next auth methods %v", err)
}
log.Debugf("next auth methods %v", methods)
return methods, err
}
case "NoneAuth":
config.NoneAuthCallback = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
log.Debugf("downstream %v is sending none auth", conn.RemoteAddr().String())
u, err := g.NoneAuthCallback(conn, challengeCtx)
if err != nil {
log.Debugf("cannot create upstream for %v with none auth: %v", conn.RemoteAddr().String(), err)
}
return u, err
}
case "PasswordAuth":
config.PasswordCallback = func(conn ssh.ConnMetadata, password []byte, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
log.Debugf("downstream %v is sending password auth", conn.RemoteAddr().String())
u, err := g.PasswordCallback(conn, password, challengeCtx)
if err != nil {
log.Debugf("cannot create upstream for %v with password auth: %v", conn.RemoteAddr().String(), err)
}
return u, err
}
case "PublicKeyAuth":
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
log.Debugf("downstream %v is sending public key auth", conn.RemoteAddr().String())
u, err := g.PublicKeyCallback(conn, key, challengeCtx)
if err != nil {
log.Debugf("cannot create upstream for %v with public key auth: %v", conn.RemoteAddr().String(), err)
}
return u, err
}
case "KeyboardInteractiveAuth":
config.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
log.Debugf("downstream %v is sending keyboard interactive auth", conn.RemoteAddr().String())
u, err := g.KeyboardInteractiveCallback(conn, challenge, challengeCtx)
if err != nil {
log.Debugf("cannot create upstream for %v with keyboard interactive auth: %v", conn.RemoteAddr().String(), err)
}
return u, err
}
case "UpstreamAuthFailure":
config.UpstreamAuthFailureCallback = func(conn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) {
log.Debugf("upstream rejected [%v] auth: %v", method, err)
g.UpstreamAuthFailureCallbackRemote(conn, method, err, challengeCtx)
}
case "Banner":
config.BannerCallback = g.BannerCallback
default:
return fmt.Errorf("unknown callback %s", c)
}
}
return nil
}
func (g *GrpcPlugin) CreatePiperConfig() (*ssh.PiperConfig, error) {
config := &ssh.PiperConfig{}
return config, g.InstallPiperConfig(config)
}
type connMeta libplugin.ConnMeta
// ChallengedUsername implements ssh.ChallengeContext
func (m *connMeta) ChallengedUsername() string {
return m.UserName
}
// Meta implements ssh.ChallengeContext
func (m *connMeta) Meta() interface{} {
return m
}
func (g *GrpcPlugin) CreateChallengeContext(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
uiq, err := uuid.NewRandom()
if err != nil {
return nil, err
}
meta := connMeta{
UserName: conn.User(),
FromAddr: conn.RemoteAddr().String(),
UniqId: uiq.String(),
}
return &meta, g.NewConnection(&meta)
}
func (g *GrpcPlugin) NewConnection(meta *connMeta) error {
if g.hasNewConnectionCallback {
_, err := g.client.NewConnection(context.Background(), &libplugin.NewConnectionRequest{
Meta: &libplugin.ConnMeta{
UserName: meta.UserName,
FromAddr: meta.FromAddr,
UniqId: meta.UniqId,
},
})
return err
}
return nil
}
func (g *GrpcPlugin) NextAuthMethodsLocal(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) {
var allow []string
for k, v := range g.allowedMethod {
if v {
allow = append(allow, k)
}
}
return allow, nil
}
func toMeta(challengeCtx ssh.ChallengeContext) *libplugin.ConnMeta {
switch meta := challengeCtx.(type) {
case *connMeta:
return (*libplugin.ConnMeta)(meta)
case *chainConnMeta:
return (*libplugin.ConnMeta)(&meta.connMeta)
}
panic("unknown challenge context")
}
func (g *GrpcPlugin) NextAuthMethodsRemote(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) {
meta := toMeta(challengeCtx)
reply, err := g.client.NextAuthMethods(context.Background(), &libplugin.NextAuthMethodsRequest{
Meta: meta,
})
if err != nil {
return nil, err
}
var methods []string
for _, method := range reply.Methods {
m := libplugin.AuthMethodTypeToName(method)
if m == "" {
continue
}
methods = append(methods, m)
}
return methods, nil
}
func (g *GrpcPlugin) UpstreamAuthFailureCallbackLocal(onn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) {
noMoreMethodErr, ok := err.(ssh.NoMoreMethodsErr)
if ok {
for _, allowed := range noMoreMethodErr.Allowed {
g.allowedMethod[allowed] = true
}
return
}
g.allowedMethod[method] = false
}
func (g *GrpcPlugin) UpstreamAuthFailureCallbackRemote(onn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) {
noMoreMethodErr, ok := err.(ssh.NoMoreMethodsErr)
allowed := make([]libplugin.AuthMethod, len(noMoreMethodErr.Allowed))
if ok {
for _, method := range noMoreMethodErr.Allowed {
m := libplugin.AuthMethodFromName(method)
if m == -1 {
continue
}
allowed = append(allowed, m)
}
}
g.client.UpstreamAuthFailureNotice(context.Background(), &libplugin.UpstreamAuthFailureNoticeRequest{
Meta: toMeta(challengeCtx),
Method: method,
Error: err.Error(),
AllowedMethods: allowed,
})
}
func (g *GrpcPlugin) createUpstream(challengeCtx ssh.ChallengeContext, upstream *libplugin.Upstream) (*ssh.Upstream, error) {
if upstream.GetNextPlugin() != nil {
if g.OnNextPlugin == nil {
return nil, fmt.Errorf("next plugin is not supported")
}
return nil, g.OnNextPlugin(challengeCtx, upstream.GetNextPlugin())
}
meta := toMeta(challengeCtx)
port := upstream.Port
if port <= 0 {
port = 22
}
addr := net.JoinHostPort(upstream.Host, strconv.Itoa(int(port)))
c, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
config := ssh.ClientConfig{
HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error {
if upstream.IgnoreHostKey {
return nil
}
verify, err := g.client.VerifyHostKey(context.Background(), &libplugin.VerifyHostKeyRequest{
Meta: meta,
Key: key.Marshal(),
})
if err != nil {
return err
}
if !verify.Verified {
return fmt.Errorf("host key verification failed")
}
return nil
},
}
auth := make([]string, 0)
if upstream.GetNone() != nil {
config.Auth = append(config.Auth, ssh.NoneAuth())
auth = append(auth, "none")
}
if a := upstream.GetPassword(); a != nil {
config.Auth = append(config.Auth, ssh.Password(a.GetPassword()))
auth = append(auth, "password")
}
if a := upstream.GetPrivateKey(); a != nil {
private, err := ssh.ParsePrivateKey(a.GetPrivateKey())
if err != nil {
return nil, err
}
config.Auth = append(config.Auth, ssh.PublicKeys(private))
auth = append(auth, "privatekey")
}
if a := upstream.GetRemoteSigner(); a != nil {
rs := remotesigner.New(grpcsigner.New(g.remotesignerClient, a.Meta))
signer, err := ssh.NewSignerFromSigner(rs)
if err != nil {
return nil, err
}
config.Auth = append(config.Auth, ssh.PublicKeys(signer))
auth = append(auth, "remotesigner")
}
if len(config.Auth) == 0 {
log.Warnf("no auth method found for upstream %s, add none auth", addr)
auth = append(auth, "none")
config.Auth = append(config.Auth, ssh.NoneAuth())
}
log.Debugf("connecting to upstream %v with auth %v", c.RemoteAddr().String(), auth)
return &ssh.Upstream{
Conn: c,
Address: addr,
ClientConfig: config,
}, nil
}
func (g *GrpcPlugin) NoneAuthCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
meta := toMeta(challengeCtx)
reply, err := g.client.NoneAuth(context.Background(), &libplugin.NoneAuthRequest{
Meta: meta,
})
if err != nil {
return nil, err
}
return g.createUpstream(challengeCtx, reply.Upstream)
}
func (g *GrpcPlugin) PasswordCallback(conn ssh.ConnMetadata, password []byte, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
meta := toMeta(challengeCtx)
reply, err := g.client.PasswordAuth(context.Background(), &libplugin.PasswordAuthRequest{
Meta: meta,
Password: password,
})
if err != nil {
return nil, err
}
return g.createUpstream(challengeCtx, reply.Upstream)
}
func (g *GrpcPlugin) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
meta := toMeta(challengeCtx)
reply, err := g.client.PublicKeyAuth(context.Background(), &libplugin.PublicKeyAuthRequest{
Meta: meta,
PublicKey: key.Marshal(),
})
if err != nil {
return nil, err
}
return g.createUpstream(challengeCtx, reply.Upstream)
}
func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) {
stream, err := g.client.KeyboardInteractiveAuth(context.Background())
if err != nil {
return nil, err
}
defer stream.CloseSend()
for {
msg, err := stream.Recv()
if err == io.EOF {
return nil, nil
}
if err != nil {
return nil, err
}
if r := msg.GetPromptRequest(); r != nil {
var questions []string
var echo []bool
for _, q := range r.GetQuestions() {
questions = append(questions, q.GetText())
echo = append(echo, q.GetEcho())
}
ans, err := client(conn.User(), r.GetInstruction(), questions, echo)
if err != nil {
return nil, err
}
if len(questions) > 0 {
if err := stream.Send(&libplugin.KeyboardInteractiveAuthMessage{
Message: &libplugin.KeyboardInteractiveAuthMessage_UserResponse{
UserResponse: &libplugin.KeyboardInteractiveUserResponse{
Answers: ans,
},
},
}); err != nil {
return nil, err
}
}
} else if r := msg.GetMetaRequest(); r != nil {
meta := toMeta(challengeCtx)
if err := stream.Send(&libplugin.KeyboardInteractiveAuthMessage{
Message: &libplugin.KeyboardInteractiveAuthMessage_MetaResponse{
MetaResponse: &libplugin.KeyboardInteractiveMetaResponse{
Meta: meta,
},
},
}); err != nil {
return nil, err
}
} else if r := msg.GetFinishRequest(); r != nil {
if r.GetUpstream() != nil {
return g.createUpstream(challengeCtx, r.GetUpstream())
}
return nil, fmt.Errorf("auth failed: %s", r.GetErrorMessage())
}
}
}
func (g *GrpcPlugin) BannerCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
meta := toMeta(challengeCtx)
reply, err := g.client.Banner(context.Background(), &libplugin.BannerRequest{
Meta: meta,
})
if err != nil {
log.Debugf("failed to get banner: %v", err)
return ""
}
return reply.GetMessage()
}
func (g *GrpcPlugin) RecvLogs(writer io.Writer) error {
stream, err := g.client.Logs(context.Background(), &libplugin.StartLogRequest{})
if err != nil {
return err
}
for {
line, err := stream.Recv()
if err != nil {
log.Errorf("recv log error: %v", err)
return err
}
fmt.Fprintln(writer, line.GetMessage())
}
}
type CmdPlugin struct {
GrpcPlugin
}
func DialCmd(cmd *exec.Cmd) (*CmdPlugin, error) {
cmdconn, stderr, err := ioconn.DialCmd(cmd)
if err != nil {
return nil, err
}
go io.Copy(log.StandardLogger().Out, stderr)
go func() {
err := cmd.Wait()
if err != nil {
log.Errorf("cmd %v error: %v", cmd.Path, err)
}
}()
conn, err := grpc.Dial("", grpc.WithInsecure(), grpc.WithDialer(func(_ string, _ time.Duration) (net.Conn, error) {
return cmdconn, nil
}))
if err != nil {
return nil, err
}
g, err := DialGrpc(conn)
if err != nil {
return nil, err
}
return &CmdPlugin{*g}, nil
}

163
cmd/sshpiperd/main.go Normal file
View file

@ -0,0 +1,163 @@
package main
import (
"fmt"
"os"
"os/exec"
"runtime/debug"
"time"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/cmd/sshpiperd/internal/plugin"
"github.com/urfave/cli/v2"
)
var mainver string = "(devel)"
func version() string {
var v = mainver
bi, ok := debug.ReadBuildInfo()
if !ok {
return v
}
for _, s := range bi.Settings {
switch s.Key {
case "vcs.revision":
v = fmt.Sprintf("%v, %v", v, s.Value[:9])
case "vcs.time":
v = fmt.Sprintf("%v, %v", v, s.Value)
}
}
v = fmt.Sprintf("%v, %v", v, bi.GoVersion)
return v
}
func splitByDash(args []string) ([]string, []string) {
for i, arg := range args {
if arg == "--" {
return args[:i], args[i+1:]
}
}
return args, nil
}
func main() {
app := &cli.App{
Name: "sshpiperd",
Usage: "the missing reverse proxy for ssh scp",
UsageText: "sshpiperd [options] <plugin1> [plugin options] [-- [plugin2] [plugin options] [-- ...]]",
Description: "sshpiperd works as a proxy-like ware, and route connections by username, src ip , etc.\nhttps://github.com/tg123/sshpiper",
Version: version(),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "address",
Aliases: []string{"l"},
Value: "0.0.0.0",
Usage: "listening address",
EnvVars: []string{"SSHPIPERD_ADDRESS"},
},
&cli.IntFlag{
Name: "port",
Aliases: []string{"p"},
Value: 2222,
Usage: "listening port",
EnvVars: []string{"SSHPIPERD_PORT"},
},
&cli.StringFlag{
Name: "server-key",
Aliases: []string{"i"},
Usage: "server key files, support wildcard",
Value: "/etc/ssh/ssh_host_rsa_key",
EnvVars: []string{"SSHPIPERD_SERVER_KEY"},
},
&cli.DurationFlag{
Name: "login-grace-time",
Value: 30 * time.Second,
Usage: "sshpiperd forcely close the connection after this time if the pipe has not successfully established",
EnvVars: []string{"SSHPIPERD_LOGIN_GRACE_TIME"},
},
&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "log level, one of: trace, debug, info, warn, error, fatal, panic",
EnvVars: []string{"SSHPIPERD_LOG_LEVEL"},
},
},
// Commands: []*cli.Command{
// &cli.Command{
// Name: "plug",
// },
// // &cli.Command{
// // Name: "grpc",
// // Action: func(ctx *cli.Context) error {
// // return fmt.Errorf("not implemented")
// // },
// // },
// },
Action: func(ctx *cli.Context) error {
level, err := log.ParseLevel(ctx.String("log-level"))
if err != nil {
return err
}
log.SetLevel(level)
log.Info("starting sshpiperd version: ", version())
d, err := newDaemon(ctx)
if err != nil {
return err
}
var plugins []*plugin.GrpcPlugin
args := ctx.Args().Slice()
remain := args
for {
if len(remain) <= 0 {
break
}
args, remain = splitByDash(remain)
if len(args) <= 0 {
continue
}
exe := args[0]
cmd := exec.Command(exe)
cmd.Args = args
log.Info("starting plugin: ", cmd.Args)
p, err := plugin.DialCmd(cmd)
if err != nil {
return err
}
go p.RecvLogs(log.StandardLogger().Out)
plugins = append(plugins, &p.GrpcPlugin)
}
if err := d.install(plugins...); err != nil {
return err
}
return d.run()
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}

2
crypto

@ -1 +1 @@
Subproject commit e6852b712aa5b99917e70720a8c27bf19fcea1b5
Subproject commit 0fa3c5e5fe2210a9beb517968e40f930398774e8

10
go.mod
View file

@ -8,6 +8,7 @@ replace (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0
github.com/dcu/go-authy v1.0.1
github.com/go-sql-driver/mysql v1.6.0
@ -20,19 +21,20 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/tg123/remotesigner v0.0.0-20210928104451-7c20285909d1
github.com/tg123/sshkey v0.0.0-20201202190454-3bb356f89f1f
github.com/urfave/cli/v2 v2.10.3
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88
google.golang.org/grpc v1.45.0
google.golang.org/grpc v1.47.0
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.22.2
k8s.io/client-go v1.5.2
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
github.com/cjlapao/common-go v0.0.19 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.11.0 // indirect
github.com/go-logr/logr v1.1.0 // indirect
@ -59,6 +61,8 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect

17
go.sum
View file

@ -71,7 +71,10 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -91,6 +94,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
@ -202,6 +206,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@ -283,6 +289,8 @@ github.com/pockost/sshpipe-k8s-lib v0.0.3/go.mod h1:gDUHQhvnDsuvrIVuVD6HCaFYEZ1i
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -307,6 +315,10 @@ github.com/tg123/remotesigner v0.0.0-20210928104451-7c20285909d1 h1:17rr5yKlOnsx
github.com/tg123/remotesigner v0.0.0-20210928104451-7c20285909d1/go.mod h1:leliuGRH9ayYYwRo6JO8yg+KyWcltK69XJdLv1x7slM=
github.com/tg123/sshkey v0.0.0-20201202190454-3bb356f89f1f h1:MOxh2uC27wne9vo2OJqtL2W0Uq3qbBnaOGKgiJA+/fI=
github.com/tg123/sshkey v0.0.0-20201202190454-3bb356f89f1f/go.mod h1:0NrddipH8l+ho+Cpbs4qrpEaqbNeHUAfhxURhKC1waE=
github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo=
github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -436,8 +448,10 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -587,6 +601,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -624,6 +640,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

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,
},
}
}

44
plugin/fixed/main.go Normal file
View file

@ -0,0 +1,44 @@
package main
import (
"os"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/libplugin"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("no target address provided")
}
target := os.Args[1]
host, port, err := libplugin.SplitHostPortForSSH(target)
if err != nil {
panic(err)
}
config := libplugin.SshPiperPluginConfig{
PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) {
log.Info("routing to ", target)
return &libplugin.Upstream{
Host: host,
Port: int32(port),
IgnoreHostKey: true,
Auth: libplugin.CreatePasswordAuth(password),
}, nil
},
}
p, err := libplugin.NewFromStdio(config)
if err != nil {
panic(err)
}
libplugin.ConfigStdioLogrus(p, nil)
log.Printf("starting fix routing to ssh %v (password only)", target)
panic(p.Serve())
}

51
plugin/simplemath/main.go Normal file
View file

@ -0,0 +1,51 @@
package main
import (
"fmt"
"math/rand"
"strconv"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/libplugin"
)
func main() {
config := libplugin.SshPiperPluginConfig{
KeyboardInteractiveCallback: func(conn libplugin.ConnMetadata, client libplugin.KeyboardInteractiveChallenge) (*libplugin.Upstream, error) {
client("lets do math", "", false)
for {
a := rand.Intn(10)
b := rand.Intn(10)
ans, err := client("", fmt.Sprintf("what is %v + %v = ", a, b), true)
if err != nil {
return nil, err
}
log.Printf("got ans = %v", ans)
if ans == fmt.Sprintf("%v", a+b) {
return &libplugin.Upstream{
Auth: libplugin.CreateNextPluginAuth(map[string]string{
"a": strconv.Itoa(a),
"b": strconv.Itoa(b),
"ans": ans,
}),
}, nil
}
}
},
}
p, err := libplugin.NewFromStdio(config)
if err != nil {
panic(err)
}
libplugin.ConfigStdioLogrus(p, nil)
log.Printf("starting simple math additional auth")
panic(p.Serve())
}