merge totp to workingdir (#476)
* merge totp to workingdir * Refactor .goreleaser.yaml to remove plugin_totp build configuration
This commit is contained in:
parent
0e6168a6b8
commit
23e18f4f7c
11 changed files with 669 additions and 791 deletions
|
|
@ -89,6 +89,16 @@ google.com:12345
|
|||
├── linode....
|
||||
```
|
||||
|
||||
## TOTP
|
||||
|
||||
`--check-totp` will check the TOTP 2FA before connecting to the upstream, compatible with all [RFC6238](https://datatracker.ietf.org/doc/html/rfc6238) authenticator, for example: `google authenticator`, `azure authenticator`.
|
||||
|
||||
the secret should be stored in `totp` file in working directory.
|
||||
for example:
|
||||
|
||||
```
|
||||
/var/sshpiper/username/totp
|
||||
```
|
||||
|
||||
## FAQ
|
||||
* Q: why sshpiperd still asks for password even I disabled password auth in upstream (different behavior from `v0`)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/tg123/sshpiper/libplugin"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
|
@ -42,6 +47,11 @@ func main() {
|
|||
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) {
|
||||
|
||||
|
|
@ -54,15 +64,66 @@ func main() {
|
|||
recursiveSearch: c.Bool("recursive-search"),
|
||||
}
|
||||
|
||||
checktotp := c.Bool("check-totp")
|
||||
|
||||
skel := libplugin.NewSkelPlugin(fac.listPipe)
|
||||
config := skel.CreateConfig()
|
||||
config.NextAuthMethodsCallback = func(_ libplugin.ConnMetadata) ([]string, error) {
|
||||
if fac.noPasswordAuth {
|
||||
return []string{"publickey"}, nil
|
||||
config.NextAuthMethodsCallback = func(conn libplugin.ConnMetadata) ([]string, error) {
|
||||
|
||||
auth := []string{"publickey"}
|
||||
|
||||
if !fac.noPasswordAuth {
|
||||
auth = append(auth, "password")
|
||||
}
|
||||
|
||||
return []string{"password", "publickey"}, nil
|
||||
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
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue