From 0dd9491d13ae0d42d5f162f9c640f9ad9451a3b6 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Thu, 21 Jul 2022 09:17:45 +0000 Subject: [PATCH] add totp plugin --- README.md | 1 + go.mod | 3 + go.sum | 4 + plugin/internal/workingdir/workingdir.go | 8 +- plugin/totp/README.md | 18 +++++ plugin/totp/main.go | 93 ++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 plugin/totp/README.md create mode 100644 plugin/totp/main.go diff --git a/README.md b/README.md index 39662e4f..c89cee06 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/go.mod b/go.mod index 79d74ef4..f948caec 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 23289d21..3b6e24d8 100644 --- a/go.sum +++ b/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= diff --git a/plugin/internal/workingdir/workingdir.go b/plugin/internal/workingdir/workingdir.go index b9d63961..d223d941 100644 --- a/plugin/internal/workingdir/workingdir.go +++ b/plugin/internal/workingdir/workingdir.go @@ -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 } diff --git a/plugin/totp/README.md b/plugin/totp/README.md new file mode 100644 index 00000000..a0913afa --- /dev/null +++ b/plugin/totp/README.md @@ -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 +``` \ No newline at end of file diff --git a/plugin/totp/main.go b/plugin/totp/main.go new file mode 100644 index 00000000..d1368c43 --- /dev/null +++ b/plugin/totp/main.go @@ -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 + }, + }) +}