add totp plugin

This commit is contained in:
Boshi Lian 2022-07-21 09:17:45 +00:00
parent 75b2c624c7
commit 0dd9491d13
6 changed files with 123 additions and 4 deletions

View file

@ -49,7 +49,7 @@ func IsUsernameSecure(user string) bool {
func (w *Workingdir) Mapkey(pub []byte) ([]byte, error) {
var rest []byte
rest, err := w.readfile(userAuthorizedKeysFile)
rest, err := w.Readfile(userAuthorizedKeysFile)
if err != nil {
return nil, err
}
@ -65,7 +65,7 @@ func (w *Workingdir) Mapkey(pub []byte) ([]byte, error) {
if bytes.Equal(authedPubkey.Marshal(), pub) {
log.Infof("found mapping key %v", w.fullpath(userKeyFile))
return w.readfile(userKeyFile)
return w.Readfile(userKeyFile)
}
}
@ -74,7 +74,7 @@ func (w *Workingdir) Mapkey(pub []byte) ([]byte, error) {
func (w *Workingdir) CreateUpstream() (*libplugin.Upstream, error) {
data, err := w.readfile(userUpstreamFile)
data, err := w.Readfile(userUpstreamFile)
if err != nil {
return nil, err
}
@ -143,7 +143,7 @@ func (w *Workingdir) fullpath(file string) string {
return path.Join(w.Path, file)
}
func (w *Workingdir) readfile(file string) ([]byte, error) {
func (w *Workingdir) Readfile(file string) ([]byte, error) {
if err := w.checkPerm(file); err != nil {
return nil, err
}

18
plugin/totp/README.md Normal file
View file

@ -0,0 +1,18 @@
# TOTP Working Directory plugin for sshpiperd
Add TOTP 2FA to ssh, compatible with all [RFC6238](https://datatracker.ietf.org/doc/html/rfc6238) authenticator, for example: `google authenticator`, `azure authenticator`.
the plugin is load `totp` in working directory defined in [workingdir](../workingdir/) plugin.
## Usage
```
./sshpiperd ./totp -- ./workingdir
```
the secret should be stored in `totp` file in working directory.
for example:
```
/var/sshpiper/username/totp
```

93
plugin/totp/main.go Normal file
View file

@ -0,0 +1,93 @@
package main
import (
"fmt"
"path"
"strings"
"github.com/pquerna/otp/totp"
"github.com/tg123/sshpiper/libplugin"
"github.com/tg123/sshpiper/plugin/internal/workingdir"
"github.com/urfave/cli/v2"
)
// type secretLoader struct {
// }
// func (s *secretLoader) Load(user string) (string, error) {
// return "", nil
// }
// TODO remove dup code
func createWorkingdir(c *cli.Context, user string) (*workingdir.Workingdir, error) {
if !c.Bool("allow-baduser-name") {
if !workingdir.IsUsernameSecure(user) {
return nil, fmt.Errorf("bad username: %s", user)
}
}
root := c.String("root")
return &workingdir.Workingdir{
Path: path.Join(root, user),
NoCheckPerm: c.Bool("no-check-perm"),
Strict: c.Bool("strict-hostkey"),
}, nil
}
func main() {
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
Name: "totp",
Usage: "sshpiperd totp 2FA authentication, workingdir based",
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"},
},
},
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
return &libplugin.SshPiperPluginConfig{
KeyboardInteractiveCallback: func(conn libplugin.ConnMetadata, client libplugin.KeyboardInteractiveChallenge) (*libplugin.Upstream, error) {
w, err := createWorkingdir(c, conn.User())
if err != nil {
return nil, err
}
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.CreateNextPluginAuth(nil),
}, nil
}
_, _ = client("", "Wrong code, please try again", "", false)
}
},
}, nil
},
})
}