sshpiper/plugin/workingdir/main.go
Josh Bleecher Snyder 297b8c60e7
extract skel into its own package (#596)
This breaks the dependency of libplugin
on x/crypto, enabling people to use libplugin
without having conflicts over the crypto module.
2025-05-23 12:42:44 -07:00

127 lines
3.4 KiB
Go

package main
import (
"fmt"
"path"
"strings"
"github.com/pquerna/otp/totp"
"github.com/tg123/sshpiper/libplugin"
"github.com/tg123/sshpiper/libplugin/skel"
"github.com/urfave/cli/v2"
)
func main() {
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
Name: "workingdir",
Usage: "sshpiperd workingdir plugin",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "root",
Usage: "path to root working directory",
Value: "/var/sshpiper",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_ROOT"},
},
&cli.BoolFlag{
Name: "allow-baduser-name",
Usage: "allow bad username",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_ALLOWBADUSERNAME"},
},
&cli.BoolFlag{
Name: "no-check-perm",
Usage: "disable 0400 checking",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_NOCHECKPERM"},
},
&cli.BoolFlag{
Name: "strict-hostkey",
Usage: "upstream host public key must be in known_hosts file, otherwise drop the connection",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_STRICTHOSTKEY"},
},
&cli.BoolFlag{
Name: "no-password-auth",
Usage: "disable password authentication and only use public key authentication",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_NOPASSWORD_AUTH"},
},
&cli.BoolFlag{
Name: "recursive-search",
Usage: "search subdirectories under user directory for upsteam",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_RECURSIVESEARCH"},
},
&cli.BoolFlag{
Name: "check-totp",
Usage: "check totp code for 2FA, totp file should be in user directory named `totp`",
EnvVars: []string{"SSHPIPERD_WORKINGDIR_CHECKTOTP"},
},
},
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
fac := workdingdirFactory{
root: c.String("root"),
allowBadUsername: c.Bool("allow-baduser-name"),
noPasswordAuth: c.Bool("no-password-auth"),
noCheckPerm: c.Bool("no-check-perm"),
strictHostKey: c.Bool("strict-hostkey"),
recursiveSearch: c.Bool("recursive-search"),
}
checktotp := c.Bool("check-totp")
skel := skel.NewSkelPlugin(fac.listPipe)
config := skel.CreateConfig()
config.NextAuthMethodsCallback = func(conn libplugin.ConnMetadata) ([]string, error) {
auth := []string{"publickey"}
if !fac.noPasswordAuth {
auth = append(auth, "password")
}
if checktotp {
if conn.GetMeta("totp") != "checked" {
auth = []string{"keyboard-interactive"}
}
}
return auth, nil
}
config.KeyboardInteractiveCallback = func(conn libplugin.ConnMetadata, client libplugin.KeyboardInteractiveChallenge) (*libplugin.Upstream, error) {
user := conn.User()
if !fac.allowBadUsername {
if !isUsernameSecure(user) {
return nil, fmt.Errorf("bad username: %s", user)
}
}
w := &workingdir{
Path: path.Join(fac.root, conn.User()),
NoCheckPerm: fac.noCheckPerm,
}
secret, err := w.Readfile("totp")
if err != nil {
return nil, err
}
for {
passcode, err := client("", "", "Authentication code:", true)
if err != nil {
return nil, err
}
if totp.Validate(passcode, strings.TrimSpace(string(secret))) {
return &libplugin.Upstream{
Auth: libplugin.CreateRetryCurrentPluginAuth(map[string]string{
"totp": "checked",
}),
}, nil
}
_, _ = client("", "Wrong code, please try again", "", false)
}
}
return config, nil
},
})
}