add totp plugin
This commit is contained in:
parent
75b2c624c7
commit
0dd9491d13
6 changed files with 123 additions and 4 deletions
|
|
@ -88,6 +88,7 @@ Plugin list
|
|||
* [yaml](plugin/yaml/) 🔀: config routing with a single yaml file.
|
||||
* [docker](plugin/docker/) 🔀: pipe into docker containers.
|
||||
* [kubernetes](plugin/kubernetes/) 🔀: manage pipes via Kubernetes CRD.
|
||||
* [totp](plugin/totp/) 🔒: TOTP 2FA plugin. compatible with all [RFC6238](https://datatracker.ietf.org/doc/html/rfc6238) authenticator, for example: `google authenticator`, `azure authenticator`.
|
||||
* [azdevicecode](plugin/azdevicecode/) 🔒: ask user to enter [azure device code](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code) before login
|
||||
* [fixed](plugin/fixed/) 🔀: fixed targetting the dummy sshd server
|
||||
* [simplemath](plugin/simplemath/) 🔒: ask for very simple math question before login, demo purpose
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -27,6 +27,8 @@ require (
|
|||
k8s.io/code-generator v0.24.3
|
||||
)
|
||||
|
||||
require github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect
|
||||
|
|
@ -71,6 +73,7 @@ require (
|
|||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.7.2 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -66,6 +66,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
|||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
|
@ -310,6 +312,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
|
|||
|
|
@ -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
18
plugin/totp/README.md
Normal 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
93
plugin/totp/main.go
Normal 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
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue