libplugin
This commit is contained in:
parent
b49669bdae
commit
dbef31dd3d
18 changed files with 5250 additions and 4 deletions
135
cmd/sshpiperd/daemon.go
Normal file
135
cmd/sshpiperd/daemon.go
Normal 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)
|
||||
}
|
||||
}
|
||||
144
cmd/sshpiperd/internal/plugin/chain.go
Normal file
144
cmd/sshpiperd/internal/plugin/chain.go
Normal 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
|
||||
}
|
||||
519
cmd/sshpiperd/internal/plugin/grpc.go
Normal file
519
cmd/sshpiperd/internal/plugin/grpc.go
Normal 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
163
cmd/sshpiperd/main.go
Normal 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
2
crypto
|
|
@ -1 +1 @@
|
|||
Subproject commit e6852b712aa5b99917e70720a8c27bf19fcea1b5
|
||||
Subproject commit 0fa3c5e5fe2210a9beb517968e40f930398774e8
|
||||
10
go.mod
10
go.mod
|
|
@ -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
17
go.sum
|
|
@ -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
3
libplugin/doc.go
Normal 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
48
libplugin/ioconn/cmd.go
Normal 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
113
libplugin/ioconn/conn.go
Normal 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
|
||||
}
|
||||
37
libplugin/ioconn/listener.go
Normal file
37
libplugin/ioconn/listener.go
Normal 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
2746
libplugin/plugin.pb.go
Normal file
File diff suppressed because it is too large
Load diff
189
libplugin/plugin.proto
Normal file
189
libplugin/plugin.proto
Normal 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
521
libplugin/plugin_grpc.pb.go
Normal 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
374
libplugin/pluginbase.go
Normal 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
138
libplugin/util.go
Normal 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
44
plugin/fixed/main.go
Normal 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
51
plugin/simplemath/main.go
Normal 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())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue