diff --git a/sshpiperd/cmd.go b/sshpiperd/cmd.go index 5d0940e8..84b0ed6a 100644 --- a/sshpiperd/cmd.go +++ b/sshpiperd/cmd.go @@ -196,9 +196,7 @@ func main() { showVersion() dumpConfig() - startPiper(&config.piperdConfig) - - return nil + return startPiper(&config.piperdConfig) } return command.Execute(args) diff --git a/sshpiperd/sshpiperd.go b/sshpiperd/sshpiperd.go index 31125841..c5d10a84 100644 --- a/sshpiperd/sshpiperd.go +++ b/sshpiperd/sshpiperd.go @@ -9,8 +9,8 @@ import ( "github.com/tg123/sshpiper/sshpiperd/auditor" "github.com/tg123/sshpiper/sshpiperd/challenger" - "github.com/tg123/sshpiper/sshpiperd/upstream" "github.com/tg123/sshpiper/sshpiperd/registry" + "github.com/tg123/sshpiper/sshpiperd/upstream" ) type piperdConfig struct { @@ -23,72 +23,120 @@ type piperdConfig struct { AuditorDriver string `long:"auditor-driver" description:"Auditor for ssh connections piped by SSH Piper " env:"SSHPIPERD_AUDITOR" ini-name:"auditor-driver"` } -func getAndInstall(name string, get func(n string) registry.Plugin, install func(plugin registry.Plugin)) { +func getAndInstall(name string, get func(n string) registry.Plugin, install func(plugin registry.Plugin) error) error { if name == "" { - return + return nil } p := get(name) if p == nil { - logger.Fatalf("driver %v not found", name) + return fmt.Errorf("driver %v not found", name) } - p.Init(logger) - install(p) + err := p.Init(logger) + if err != nil { + return err + } + return install(p) } -func startPiper(config *piperdConfig) { +func installDrivers(piper *ssh.SSHPiperConfig, config *piperdConfig) (auditor.Provider, error) { + + // install upstreamProvider driver + if config.UpstreamDriver == "" { + return nil, fmt.Errorf("must provider upstream driver") + } + + var bigbro auditor.Provider + + for _, d := range []struct { + name string + get func(n string) registry.Plugin + install func(plugin registry.Plugin) error + }{ + // upstream driver + { + config.UpstreamDriver, + func(n string) registry.Plugin { + return upstream.Get(n) + }, + func(plugin registry.Plugin) error { + handler := plugin.(upstream.Provider).GetHandler() + + if handler == nil { + return fmt.Errorf("driver return nil handler") + } + + piper.FindUpstream = handler + return nil + }, + }, + // challenger driver + { + config.ChallengerDriver, + func(n string) registry.Plugin { + return challenger.Get(n) + }, + func(plugin registry.Plugin) error { + handler := plugin.(challenger.Provider).GetHandler() + + if handler == nil { + return fmt.Errorf("driver return nil handler") + } + + piper.AdditionalChallenge = handler + return nil + }, + }, + // auditor driver + { + config.AuditorDriver, + func(n string) registry.Plugin { + return auditor.Get(n) + }, + func(plugin registry.Plugin) error { + bigbro = plugin.(auditor.Provider) + return nil + }, + }, + } { + err := getAndInstall(d.name, d.get, d.install) + if err != nil { + return nil, err + } + } + + return bigbro, nil +} + +func startPiper(config *piperdConfig) error { logger.Println("sshpiper is about to start") piper := &ssh.SSHPiperConfig{} - // install upstreamProvider driver - if config.UpstreamDriver == "" { - logger.Fatalf("must provider upstream driver") - } - - getAndInstall(config.UpstreamDriver, func(n string) registry.Plugin { - return upstream.Get(n) - }, func(plugin registry.Plugin) { - piper.FindUpstream = plugin.(upstream.Provider).GetHandler() - }) - - // install challenger - getAndInstall(config.UpstreamDriver, func(n string) registry.Plugin { - return challenger.Get(n) - }, func(plugin registry.Plugin) { - piper.AdditionalChallenge = plugin.(challenger.Provider).GetHandler() - }) - - // install auditor - var bigbro auditor.Provider - getAndInstall(config.UpstreamDriver, func(n string) registry.Plugin { - return auditor.Get(n) - }, func(plugin registry.Plugin) { - bigbro = plugin.(auditor.Provider) - }) + bigbro, err := installDrivers(piper, config) privateBytes, err := ioutil.ReadFile(config.PiperKeyFile) if err != nil { - logger.Fatalln(err) + return err } private, err := ssh.ParsePrivateKey(privateBytes) if err != nil { - logger.Fatalln(err) + return err } piper.AddHostKey(private) listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddr, config.Port)) if err != nil { - logger.Fatalf("failed to listen for connection: %v", err) + return fmt.Errorf("failed to listen for connection: %v", err) } defer listener.Close() - logger.Printf("SSHPiperd started") + logger.Printf("sshpiperd started") for { c, err := listener.Accept() @@ -122,4 +170,6 @@ func startPiper(config *piperdConfig) { logger.Printf("connection from %v closed reason: %v", c.RemoteAddr(), err) }() } + + return nil } diff --git a/sshpiperd/sshpiperd_test.go b/sshpiperd/sshpiperd_test.go index 5d1aa80c..51607c0f 100644 --- a/sshpiperd/sshpiperd_test.go +++ b/sshpiperd/sshpiperd_test.go @@ -1,20 +1,280 @@ package main import ( + "log" "testing" + "fmt" + "github.com/tg123/sshpiper/sshpiperd/auditor" + "github.com/tg123/sshpiper/sshpiperd/challenger" "github.com/tg123/sshpiper/sshpiperd/registry" + "github.com/tg123/sshpiper/sshpiperd/upstream" + "golang.org/x/crypto/ssh" + "net" + "time" ) +type testplugin struct { + name string + init func(logger *log.Logger) error +} + +func (p *testplugin) GetName() string { + return p.name +} + +func (p *testplugin) GetOpts() interface{} { + return nil +} + +func (p *testplugin) Init(logger *log.Logger) error { + if p.init == nil { + return nil + } + return p.init(logger) +} + func Test_getAndInstall(t *testing.T) { // ignore empty getAndInstall("", func(n string) registry.Plugin { t.Errorf("should not call get") return nil - }, func(plugin registry.Plugin) { + }, func(plugin registry.Plugin) error { t.Errorf("should not call install") + return nil }) + // fail when not found + err := getAndInstall("test", func(n string) registry.Plugin { + if n != "test" { + t.Errorf("plugin name changed") + } + return nil + }, func(plugin registry.Plugin) error { + t.Errorf("should not call install") + return nil + }) + if err == nil { + t.Errorf("should err when not found") + } + + // init err + err = getAndInstall("test", func(n string) registry.Plugin { + return &testplugin{ + init: func(logger *log.Logger) error { + return fmt.Errorf("init failed") + }, + } + }, func(plugin registry.Plugin) error { + return fmt.Errorf("test") + }) + + if err == nil { + t.Errorf("should err when not found") + } + + // call init + inited := false + installed := false + err = getAndInstall("test", func(n string) registry.Plugin { + if n != "test" { + t.Errorf("plugin name changed") + } + return &testplugin{ + init: func(logger *log.Logger) error { + inited = true + return nil + }, + } + }, func(plugin registry.Plugin) error { + if !inited { + t.Errorf("not inited") + } + + installed = true + return nil + }) + + if !installed { + t.Errorf("not installed") + } + + if err != nil { + t.Errorf("should err when not found") + } +} + +type testupstream struct { + testplugin + + h upstream.Handler +} + +func (t *testupstream) GetHandler() upstream.Handler { + return t.h +} + +type testchallenger struct { + testplugin + + h challenger.Handler +} + +func (t *testchallenger) GetHandler() challenger.Handler { + return t.h +} + +type testauditorprovider struct { + testplugin + + a auditor.Auditor +} + +func (t *testauditorprovider) Create(ssh.ConnMetadata) (auditor.Auditor, error) { + return t.a, nil +} + +type testauditor struct { + up auditor.Hook + down auditor.Hook +} + +func (t *testauditor) GetUpstreamHook() auditor.Hook { + return t.up +} + +func (t *testauditor) GetDownstreamHook() auditor.Hook { + return t.down +} + +func (t *testauditor) Close() error { + return nil +} + +func Test_installDriver(t *testing.T) { + var ( + upstreamName = fmt.Sprintf("u_%v", time.Now().UTC().UnixNano()) + challengerName = fmt.Sprintf("c_%v", time.Now().UTC().UnixNano()) + auditorName = fmt.Sprintf("a_%v", time.Now().UTC().UnixNano()) + ) + + findUpstream := func(conn ssh.ConnMetadata) (net.Conn, *ssh.SSHPiperAuthPipe, error) { + return nil, nil, nil + } + + upstream.Register(upstreamName, &testupstream{ + testplugin: testplugin{ + name: upstreamName, + }, + h: findUpstream, + }) + + challenger.Register(challengerName, &testchallenger{ + testplugin: testplugin{ + name: upstreamName, + }, + h: func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (bool, error) { + return true, nil + }, + }) + + auditor.Register(auditorName, &testauditorprovider{}) + + // empty driver name + { + piper := &ssh.SSHPiperConfig{} + _, err := installDrivers(piper, &piperdConfig{ + UpstreamDriver: "", + }) + + if err == nil { + t.Errorf("should fail when empty driver") + } + } + + // install upstream + { + piper := &ssh.SSHPiperConfig{} + _, err := installDrivers(piper, &piperdConfig{ + UpstreamDriver: upstreamName, + }) + + if err != nil { + t.Errorf("install failed %v", err) + } + + if _, _, err := piper.FindUpstream(nil); err != nil { + t.Errorf("install wrong func") + } + + if piper.AdditionalChallenge != nil { + t.Errorf("should not install challenger") + } + } + + // install challenger + { + piper := &ssh.SSHPiperConfig{} + _, err := installDrivers(piper, &piperdConfig{ + UpstreamDriver: upstreamName, + ChallengerDriver: challengerName, + }) + + if err != nil { + t.Errorf("install failed %v", err) + } + + if ok, err := piper.AdditionalChallenge(nil, nil); err != nil || !ok { + t.Errorf("should install challenger") + } + } + + // install auditor + { + piper := &ssh.SSHPiperConfig{} + ap, err := installDrivers(piper, &piperdConfig{ + UpstreamDriver: upstreamName, + AuditorDriver: auditorName, + }) + + if err != nil { + t.Errorf("install failed %v", err) + } + + if ap == nil { + t.Errorf("nil auditor provider") + } + + ap0 := ap.(*testauditorprovider) + + ap0.a = &testauditor{ + up: func(conn ssh.ConnMetadata, msg []byte) ([]byte, error) { + msg[0] = 42 + return msg, nil + }, + down: func(conn ssh.ConnMetadata, msg []byte) ([]byte, error) { + msg[0] = 100 + return msg, nil + }, + } + + a, err := ap.Create(nil) + if err != nil { + t.Errorf("install failed %v", err) + } + + m := []byte{0} + + a.GetUpstreamHook()(nil, m) + if m[0] != 42 { + t.Errorf("upstream not handled") + } + + a.GetDownstreamHook()(nil, m) + if m[0] != 100 { + t.Errorf("downstream not handled") + } + } } diff --git a/sshpiperd/ver.go b/sshpiperd/ver.go index 3bda07b9..9bceb6e7 100644 --- a/sshpiperd/ver.go +++ b/sshpiperd/ver.go @@ -11,12 +11,12 @@ var githash = "0000000000" func showVersion() { versionTemplate := template.Must(template.New("ver").Parse(` -SSHPiper ver: {{.VER}} by Boshi Lian +sshpiperd by Boshi Lian https://github.com/tg123/sshpiper -go runtime : {{.GOVER}} -git hash : {{.GITHASH}} - +Version : {{.VER}} +Go Runtime : {{.GOVER}} +Git Commit : {{.GITHASH}} `[1:])) versionTemplate.Execute(os.Stdout, struct {