2017-09-30 19:12:43 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2018-11-16 02:38:18 +08:00
|
|
|
"math"
|
2017-11-14 08:29:25 +08:00
|
|
|
"math/rand"
|
2018-01-01 17:41:21 +08:00
|
|
|
"net"
|
2017-09-30 19:12:43 +08:00
|
|
|
"os"
|
|
|
|
"path"
|
2017-11-14 08:29:25 +08:00
|
|
|
"time"
|
2017-09-30 19:12:43 +08:00
|
|
|
|
|
|
|
"github.com/gliderlabs/ssh"
|
2017-10-30 23:48:14 +08:00
|
|
|
"github.com/jinzhu/gorm"
|
2017-10-31 00:12:04 +08:00
|
|
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
2017-10-30 23:48:14 +08:00
|
|
|
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
2017-09-30 19:12:43 +08:00
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
2017-11-14 07:38:23 +08:00
|
|
|
var (
|
2017-12-04 01:18:17 +08:00
|
|
|
// Version should be updated by hand at each release
|
2018-04-03 04:30:19 +08:00
|
|
|
Version = "1.8.0+dev"
|
2017-12-04 01:18:17 +08:00
|
|
|
// GitTag will be overwritten automatically by the build system
|
|
|
|
GitTag string
|
|
|
|
// GitSha will be overwritten automatically by the build system
|
|
|
|
GitSha string
|
|
|
|
// GitBranch will be overwritten automatically by the build system
|
|
|
|
GitBranch string
|
2017-11-14 07:38:23 +08:00
|
|
|
)
|
2017-11-02 05:09:08 +08:00
|
|
|
|
2017-09-30 19:12:43 +08:00
|
|
|
func main() {
|
2017-11-14 08:29:25 +08:00
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
2017-09-30 19:12:43 +08:00
|
|
|
app := cli.NewApp()
|
|
|
|
app.Name = path.Base(os.Args[0])
|
|
|
|
app.Author = "Manfred Touron"
|
2017-12-04 01:18:17 +08:00
|
|
|
app.Version = Version + " (" + GitSha + ")"
|
2017-09-30 19:12:43 +08:00
|
|
|
app.Email = "https://github.com/moul/sshportal"
|
2017-12-31 23:31:25 +08:00
|
|
|
app.Commands = []cli.Command{
|
|
|
|
{
|
2018-01-07 02:46:00 +08:00
|
|
|
Name: "server",
|
|
|
|
Usage: "Start sshportal server",
|
|
|
|
Action: func(c *cli.Context) error {
|
|
|
|
if err := ensureLogDirectory(c.String("logs-location")); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg, err := parseServeConfig(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return server(cfg)
|
|
|
|
},
|
2017-12-31 23:31:25 +08:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "bind-address, b",
|
|
|
|
EnvVar: "SSHPORTAL_BIND",
|
|
|
|
Value: ":2222",
|
|
|
|
Usage: "SSH server bind address",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "db-driver",
|
|
|
|
Value: "sqlite3",
|
|
|
|
Usage: "GORM driver (sqlite3)",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "db-conn",
|
|
|
|
Value: "./sshportal.db",
|
|
|
|
Usage: "GORM connection string",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "debug, D",
|
|
|
|
Usage: "Display debug information",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "aes-key",
|
|
|
|
Usage: "Encrypt sensitive data in database (length: 16, 24 or 32)",
|
|
|
|
},
|
2018-01-02 23:31:34 +08:00
|
|
|
cli.StringFlag{
|
2018-01-05 18:02:13 +08:00
|
|
|
Name: "logs-location",
|
2018-01-04 18:49:24 +08:00
|
|
|
Value: "./log",
|
2018-01-02 23:31:34 +08:00
|
|
|
Usage: "Store user session files",
|
|
|
|
},
|
2018-11-16 02:38:18 +08:00
|
|
|
cli.DurationFlag{
|
|
|
|
Name: "idle-timeout",
|
|
|
|
Value: 0,
|
|
|
|
Usage: "Duration before an inactive connection is timed out (0 to disable)",
|
|
|
|
},
|
2017-12-31 23:31:25 +08:00
|
|
|
},
|
2018-01-01 17:41:21 +08:00
|
|
|
}, {
|
|
|
|
Name: "healthcheck",
|
2018-01-07 02:46:00 +08:00
|
|
|
Action: func(c *cli.Context) error { return healthcheck(c.String("addr"), c.Bool("wait"), c.Bool("quiet")) },
|
2018-01-01 17:41:21 +08:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "addr, a",
|
|
|
|
Value: "localhost:2222",
|
2018-01-01 17:54:58 +08:00
|
|
|
Usage: "sshportal server address",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "wait, w",
|
|
|
|
Usage: "Loop indefinitely until sshportal is ready",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "quiet, q",
|
|
|
|
Usage: "Do not print errors, if any",
|
2018-01-01 17:41:21 +08:00
|
|
|
},
|
|
|
|
},
|
2018-01-02 17:57:18 +08:00
|
|
|
}, {
|
|
|
|
Name: "_test_server",
|
|
|
|
Hidden: true,
|
|
|
|
Action: testServer,
|
2017-11-24 21:29:41 +08:00
|
|
|
},
|
2017-09-30 19:12:43 +08:00
|
|
|
}
|
2017-11-02 17:32:35 +08:00
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
|
|
log.Fatalf("error: %v", err)
|
|
|
|
}
|
2017-09-30 19:12:43 +08:00
|
|
|
}
|
|
|
|
|
2018-01-07 02:46:00 +08:00
|
|
|
func server(c *configServe) (err error) {
|
|
|
|
var db = (*gorm.DB)(nil)
|
|
|
|
|
|
|
|
// try to setup the local DB
|
|
|
|
if db, err = gorm.Open(c.dbDriver, c.dbURL); err != nil {
|
|
|
|
return
|
2017-10-30 23:48:14 +08:00
|
|
|
}
|
2017-12-04 01:18:17 +08:00
|
|
|
defer func() {
|
2018-01-07 02:46:00 +08:00
|
|
|
origErr := err
|
|
|
|
err = db.Close()
|
|
|
|
if origErr != nil {
|
|
|
|
err = origErr
|
2017-12-04 01:18:17 +08:00
|
|
|
}
|
|
|
|
}()
|
2017-11-19 08:18:17 +08:00
|
|
|
if err = db.DB().Ping(); err != nil {
|
2018-01-07 02:46:00 +08:00
|
|
|
return
|
2017-10-31 00:12:04 +08:00
|
|
|
}
|
2018-01-07 02:46:00 +08:00
|
|
|
db.LogMode(c.debug)
|
2017-12-28 05:43:21 +08:00
|
|
|
if err = dbInit(db); err != nil {
|
2018-01-07 02:46:00 +08:00
|
|
|
return
|
2018-01-04 20:41:14 +08:00
|
|
|
}
|
2018-01-05 18:02:13 +08:00
|
|
|
|
2017-12-31 17:38:33 +08:00
|
|
|
// create TCP listening socket
|
2018-01-07 02:46:00 +08:00
|
|
|
ln, err := net.Listen("tcp", c.bindAddr)
|
2017-12-28 05:43:21 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-12-31 17:38:33 +08:00
|
|
|
|
|
|
|
// configure server
|
|
|
|
srv := &ssh.Server{
|
2018-01-07 02:46:00 +08:00
|
|
|
Addr: c.bindAddr,
|
2017-12-31 17:38:33 +08:00
|
|
|
Handler: shellHandler, // ssh.Server.Handler is the handler for the DefaultSessionHandler
|
|
|
|
Version: fmt.Sprintf("sshportal-%s", Version),
|
|
|
|
ChannelHandler: channelHandler,
|
|
|
|
}
|
2018-11-16 02:38:18 +08:00
|
|
|
if c.idleTimeout != 0 {
|
|
|
|
srv.IdleTimeout = c.idleTimeout
|
|
|
|
// gliderlabs/ssh requires MaxTimeout to be non-zero if we want to use IdleTimeout.
|
|
|
|
// So, set it to the max value, because we don't want a max timeout.
|
|
|
|
srv.MaxTimeout = math.MaxInt64
|
|
|
|
}
|
2018-01-07 02:46:00 +08:00
|
|
|
|
|
|
|
for _, opt := range []ssh.Option{
|
|
|
|
// custom PublicKeyAuth handler
|
|
|
|
ssh.PublicKeyAuth(publicKeyAuthHandler(db, c)),
|
|
|
|
ssh.PasswordAuth(passwordAuthHandler(db, c)),
|
|
|
|
// retrieve sshportal SSH private key from database
|
|
|
|
privateKeyFromDB(db, c.aesKey),
|
|
|
|
} {
|
2017-12-28 05:43:21 +08:00
|
|
|
if err := srv.SetOption(opt); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-07 02:46:00 +08:00
|
|
|
log.Printf("info: SSH Server accepting connections on %s", c.bindAddr)
|
2017-12-28 05:43:21 +08:00
|
|
|
return srv.Serve(ln)
|
2017-09-30 19:12:43 +08:00
|
|
|
}
|