From fd0a0c628e3934c78faa07f26661fbabbaec9f8f Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Mon, 4 Jul 2022 15:55:06 +0000 Subject: [PATCH] first move of working dir --- cmd/sshpiperd/internal/plugin/grpc.go | 35 +++++---- go.mod | 4 - go.sum | 7 -- libplugin/pluginbase.go | 14 +++- plugin/workingdir/main.go | 101 ++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 plugin/workingdir/main.go diff --git a/cmd/sshpiperd/internal/plugin/grpc.go b/cmd/sshpiperd/internal/plugin/grpc.go index aea305cf..ce772eb0 100644 --- a/cmd/sshpiperd/internal/plugin/grpc.go +++ b/cmd/sshpiperd/internal/plugin/grpc.go @@ -116,6 +116,8 @@ func (g *GrpcPlugin) InstallPiperConfig(config *ssh.PiperConfig) error { } case "Banner": config.BannerCallback = g.BannerCallback + case "VerifyHostKey": + // ignore default: return fmt.Errorf("unknown callback %s", c) } @@ -184,11 +186,13 @@ func (g *GrpcPlugin) NextAuthMethodsLocal(conn ssh.ConnMetadata, challengeCtx ss return allow, nil } -func toMeta(challengeCtx ssh.ChallengeContext) *libplugin.ConnMeta { +func toMeta(challengeCtx ssh.ChallengeContext, conn ssh.ConnMetadata) *libplugin.ConnMeta { switch meta := challengeCtx.(type) { case *connMeta: + meta.UserName = conn.User() return (*libplugin.ConnMeta)(meta) case *chainConnMeta: + meta.UserName = conn.User() return (*libplugin.ConnMeta)(&meta.connMeta) } @@ -196,7 +200,7 @@ func toMeta(challengeCtx ssh.ChallengeContext) *libplugin.ConnMeta { } func (g *GrpcPlugin) NextAuthMethodsRemote(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) ([]string, error) { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) reply, err := g.client.NextAuthMethods(context.Background(), &libplugin.NextAuthMethodsRequest{ Meta: meta, }) @@ -231,7 +235,7 @@ func (g *GrpcPlugin) UpstreamAuthFailureCallbackLocal(onn ssh.ConnMetadata, meth g.allowedMethod[method] = false } -func (g *GrpcPlugin) UpstreamAuthFailureCallbackRemote(onn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) { +func (g *GrpcPlugin) UpstreamAuthFailureCallbackRemote(conn ssh.ConnMetadata, method string, err error, challengeCtx ssh.ChallengeContext) { noMoreMethodErr, ok := err.(ssh.NoMoreMethodsErr) allowed := make([]libplugin.AuthMethod, len(noMoreMethodErr.Allowed)) if ok { @@ -246,14 +250,14 @@ func (g *GrpcPlugin) UpstreamAuthFailureCallbackRemote(onn ssh.ConnMetadata, met } g.client.UpstreamAuthFailureNotice(context.Background(), &libplugin.UpstreamAuthFailureNoticeRequest{ - Meta: toMeta(challengeCtx), + Meta: toMeta(challengeCtx, conn), Method: method, Error: err.Error(), AllowedMethods: allowed, }) } -func (g *GrpcPlugin) createUpstream(challengeCtx ssh.ChallengeContext, upstream *libplugin.Upstream) (*ssh.Upstream, error) { +func (g *GrpcPlugin) createUpstream(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext, upstream *libplugin.Upstream) (*ssh.Upstream, error) { if upstream.GetNextPlugin() != nil { if g.OnNextPlugin == nil { return nil, fmt.Errorf("next plugin is not supported") @@ -261,7 +265,7 @@ func (g *GrpcPlugin) createUpstream(challengeCtx ssh.ChallengeContext, upstream return nil, g.OnNextPlugin(challengeCtx, upstream.GetNextPlugin()) } - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) port := upstream.Port if port <= 0 { @@ -275,6 +279,7 @@ func (g *GrpcPlugin) createUpstream(challengeCtx ssh.ChallengeContext, upstream } config := ssh.ClientConfig{ + User: upstream.UserName, HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error { if upstream.IgnoreHostKey { return nil @@ -345,7 +350,7 @@ func (g *GrpcPlugin) createUpstream(challengeCtx ssh.ChallengeContext, upstream } func (g *GrpcPlugin) NoneAuthCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) reply, err := g.client.NoneAuth(context.Background(), &libplugin.NoneAuthRequest{ Meta: meta, }) @@ -354,11 +359,11 @@ func (g *GrpcPlugin) NoneAuthCallback(conn ssh.ConnMetadata, challengeCtx ssh.Ch return nil, err } - return g.createUpstream(challengeCtx, reply.Upstream) + return g.createUpstream(conn, challengeCtx, reply.Upstream) } func (g *GrpcPlugin) PasswordCallback(conn ssh.ConnMetadata, password []byte, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) reply, err := g.client.PasswordAuth(context.Background(), &libplugin.PasswordAuthRequest{ Meta: meta, Password: password, @@ -368,11 +373,11 @@ func (g *GrpcPlugin) PasswordCallback(conn ssh.ConnMetadata, password []byte, ch return nil, err } - return g.createUpstream(challengeCtx, reply.Upstream) + return g.createUpstream(conn, challengeCtx, reply.Upstream) } func (g *GrpcPlugin) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) reply, err := g.client.PublicKeyAuth(context.Background(), &libplugin.PublicKeyAuthRequest{ Meta: meta, PublicKey: key.Marshal(), @@ -382,7 +387,7 @@ func (g *GrpcPlugin) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey, return nil, err } - return g.createUpstream(challengeCtx, reply.Upstream) + return g.createUpstream(conn, challengeCtx, reply.Upstream) } func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge, challengeCtx ssh.ChallengeContext) (*ssh.Upstream, error) { @@ -430,7 +435,7 @@ func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client s } } } else if r := msg.GetMetaRequest(); r != nil { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) if err := stream.Send(&libplugin.KeyboardInteractiveAuthMessage{ Message: &libplugin.KeyboardInteractiveAuthMessage_MetaResponse{ MetaResponse: &libplugin.KeyboardInteractiveMetaResponse{ @@ -443,7 +448,7 @@ func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client s } else if r := msg.GetFinishRequest(); r != nil { if r.GetUpstream() != nil { - return g.createUpstream(challengeCtx, r.GetUpstream()) + return g.createUpstream(conn, challengeCtx, r.GetUpstream()) } return nil, fmt.Errorf("auth failed: %s", r.GetErrorMessage()) @@ -452,7 +457,7 @@ func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client s } func (g *GrpcPlugin) BannerCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string { - meta := toMeta(challengeCtx) + meta := toMeta(challengeCtx, conn) reply, err := g.client.Banner(context.Background(), &libplugin.BannerRequest{ Meta: meta, }) diff --git a/go.mod b/go.mod index 647be9be..4d1d42a5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ replace ( require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 - github.com/dcu/go-authy v1.0.1 github.com/go-sql-driver/mysql v1.6.0 github.com/google/uuid v1.3.0 github.com/jessevdk/go-flags v1.5.0 @@ -39,8 +38,6 @@ require ( github.com/denisenkom/go-mssqldb v0.11.0 // indirect github.com/go-logr/logr v1.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/gojektech/heimdall v5.0.2+incompatible // indirect - github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -60,7 +57,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect diff --git a/go.sum b/go.sum index a8679897..5bb5157d 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dcu/go-authy v1.0.1 h1:9LtF0otuGKQOD0AzyAUKzT+etvzGZxpvf4x20/xiW1Y= -github.com/dcu/go-authy v1.0.1/go.mod h1:SJ8cuAYQ9c9ZGGIGNk5gyH87/Q0uJXzhb1DR8MNnn98= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= @@ -122,10 +120,6 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gojektech/heimdall v5.0.2+incompatible h1:mfGLnHNTKN7b1OMTO4ZvL3oT2P13kqTTV7owK7BZDck= -github.com/gojektech/heimdall v5.0.2+incompatible/go.mod h1:8hRIZ3+Kz0r3GAFI9QrUuvZht8ypg5Rs8schCXioLOo= -github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 h1:MO2DsGCZz8phRhLnpFvHEQgTH521sVN/6F2GZTbNO3Q= -github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45/go.mod h1:tDYRk1s5Pms6XJjj5m2PxAzmQvaDU8GqDf1u6x7yxKw= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= @@ -276,7 +270,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= -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= diff --git a/libplugin/pluginbase.go b/libplugin/pluginbase.go index 06ac14b9..202076be 100644 --- a/libplugin/pluginbase.go +++ b/libplugin/pluginbase.go @@ -49,7 +49,7 @@ type SshPiperPluginConfig struct { KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Upstream, error) - UpstreamAuthFailureCallback func(conn ConnMetadata, method string, err error) + UpstreamAuthFailureCallback func(conn ConnMetadata, method string, err error, allowmethods []string) BannerCallback func(conn ConnMetadata) string @@ -341,7 +341,17 @@ func (s *server) UpstreamAuthFailureNotice(ctx context.Context, req *UpstreamAut return nil, status.Errorf(codes.Unimplemented, "method UpstreamAuthFailureNotice not implemented") } - s.config.UpstreamAuthFailureCallback(req.Meta, req.Method, fmt.Errorf(req.Error)) + var methods []string + + for _, method := range req.GetAllowedMethods() { + m := AuthMethodTypeToName(method) + if m == "" { + continue + } + methods = append(methods, m) + } + + s.config.UpstreamAuthFailureCallback(req.Meta, req.Method, fmt.Errorf(req.Error), methods) return &UpstreamAuthFailureNoticeResponse{}, nil } diff --git a/plugin/workingdir/main.go b/plugin/workingdir/main.go new file mode 100644 index 00000000..f44bff9e --- /dev/null +++ b/plugin/workingdir/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + "path" + + "github.com/tg123/sshpiper/libplugin" + "github.com/urfave/cli/v2" +) + +func createWorkingdir(c *cli.Context, user string) (*workingdir, error) { + if !c.Bool("allow-baduser-name") { + if !isUsernameSecure(user) { + return nil, fmt.Errorf("bad username: %s", user) + } + } + + root := c.String("root") + + return &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: "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.StringFlag{ + // Name: "fallback-username", + // Usage: "fallback to a user when user does not exists in directory", + // EnvVars: []string{"SSHPIPERD_WORKINGDIR_FALLBACKUSERNAME"}, + // }, + &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"}, + }, + }, + CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) { + + return &libplugin.SshPiperPluginConfig{ + PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) { + w, err := createWorkingdir(c, conn.User()) + if err != nil { + return nil, err + } + + u, err := w.createUpstream() + if err != nil { + return nil, err + } + + u.Auth = libplugin.CreatePasswordAuth(password) + return u, nil + }, + + PublicKeyCallback: func(conn libplugin.ConnMetadata, key []byte) (*libplugin.Upstream, error) { + w, err := createWorkingdir(c, conn.User()) + if err != nil { + return nil, err + } + + u, err := w.createUpstream() + if err != nil { + return nil, err + } + + k, err := w.mapkey(key) + u.Auth = libplugin.CreatePrivateKeyAuth(k) + + return u, nil + }, + + VerifyHostKeyCallback: func(conn libplugin.ConnMetadata, key []byte) (bool, error) { + return true, nil + }, + }, nil + }, + }) +}