Add upstream banner mode and refactor banner callbacks (#594)

* feat: add upstream banner mode and refactor banner callbacks

* chore: update crypto submodule to latest commit

* fix: improve usage description for upstream banner mode flag

* Update cmd/sshpiperd/daemon.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Boshi Lian 2025-05-19 12:31:04 -07:00 committed by GitHub
parent 2c2d378f86
commit e6d3bb694b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 31 additions and 12 deletions

View file

@ -134,7 +134,7 @@ func newDaemon(ctx *cli.Context) (*daemon, error) {
bannerfile := ctx.String("banner-file")
if bannertext != "" || bannerfile != "" {
config.BannerCallback = func(_ ssh.ConnMetadata, _ ssh.ChallengeContext) string {
config.DownstreamBannerCallback = func(_ ssh.ConnMetadata, _ ssh.ChallengeContext) string {
if bannerfile != "" {
text, err := os.ReadFile(bannerfile)
if err != nil {
@ -147,6 +147,18 @@ func newDaemon(ctx *cli.Context) (*daemon, error) {
}
}
switch ctx.String("upstream-banner-mode") {
case "passthrough":
// library will handle the banner to client
case "ignore":
config.UpstreamBannerCallback = func(downconn ssh.ServerPreAuthConn, banner string, challengeCtx ssh.ChallengeContext) error {
return nil
}
// case "dedup":
default:
return nil, fmt.Errorf("unknown upstream banner mode %q; allowed: 'passthrough' or 'ignore'", ctx.String("upstream-banner-mode"))
}
return &daemon{
config: config,
lis: lis,

View file

@ -54,7 +54,7 @@ type chainConnMeta struct {
current int
}
func (cp *ChainPlugins) CreateChallengeContext(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
func (cp *ChainPlugins) CreateChallengeContext(conn ssh.ServerPreAuthConn) (ssh.ChallengeContext, error) {
uiq, err := uuid.NewRandom()
if err != nil {
return nil, err
@ -109,7 +109,7 @@ func (cp *ChainPlugins) NextAuthMethods(conn ssh.ConnMetadata, challengeCtx ssh.
func (cp *ChainPlugins) InstallPiperConfig(config *GrpcPluginConfig) error {
config.CreateChallengeContext = func(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
config.CreateChallengeContext = func(conn ssh.ServerPreAuthConn) (ssh.ChallengeContext, error) {
ctx, err := cp.CreateChallengeContext(conn)
if err != nil {
log.Errorf("cannot create challenge context %v", err)
@ -143,10 +143,10 @@ func (cp *ChainPlugins) InstallPiperConfig(config *GrpcPluginConfig) error {
}
}
config.BannerCallback = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
config.DownstreamBannerCallback = func(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
cur := cp.pluginsCallback[challengeCtx.(*chainConnMeta).current]
if cur.BannerCallback != nil {
return cur.BannerCallback(conn, challengeCtx)
if cur.DownstreamBannerCallback != nil {
return cur.DownstreamBannerCallback(conn, challengeCtx)
}
return ""

View file

@ -56,7 +56,7 @@ func (g *GrpcPlugin) InstallPiperConfig(config *GrpcPluginConfig) error {
return err
}
config.CreateChallengeContext = func(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
config.CreateChallengeContext = func(conn ssh.ServerPreAuthConn) (ssh.ChallengeContext, error) {
ctx, err := g.CreateChallengeContext(conn)
if err != nil {
log.Errorf("cannot create challenge context %v", err)
@ -121,7 +121,7 @@ func (g *GrpcPlugin) InstallPiperConfig(config *GrpcPluginConfig) error {
g.UpstreamAuthFailureCallbackRemote(conn, method, err, challengeCtx)
}
case "Banner":
config.BannerCallback = g.BannerCallback
config.DownstreamBannerCallback = g.DownstreamBannerCallback
case "VerifyHostKey":
// ignore
case "PipeStart":
@ -155,7 +155,7 @@ func (m *connMeta) Meta() interface{} {
return m
}
func (g *GrpcPlugin) CreateChallengeContext(conn ssh.ConnMetadata) (ssh.ChallengeContext, error) {
func (g *GrpcPlugin) CreateChallengeContext(conn ssh.ServerPreAuthConn) (ssh.ChallengeContext, error) {
uiq, err := uuid.NewRandom()
if err != nil {
return nil, err
@ -494,7 +494,7 @@ func (g *GrpcPlugin) KeyboardInteractiveCallback(conn ssh.ConnMetadata, client s
}
}
func (g *GrpcPlugin) BannerCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
func (g *GrpcPlugin) DownstreamBannerCallback(conn ssh.ConnMetadata, challengeCtx ssh.ChallengeContext) string {
meta := toMeta(challengeCtx, conn)
reply, err := g.client.Banner(context.Background(), &libplugin.BannerRequest{
Meta: meta,

View file

@ -166,6 +166,12 @@ func main() {
Usage: "display a banner from file before authentication",
EnvVars: []string{"SSHPIPERD_BANNERFILE"},
},
&cli.StringFlag{
Name: "upstream-banner-mode",
Value: "passthrough",
Usage: "upstream banner mode, allowed values: 'passthrough' (pass the banner from upstream to client) or 'ignore' (ignore the banner from upstream)",
EnvVars: []string{"SSHPIPERD_UPSTREAM_BANNER_MODE"},
},
&cli.BoolFlag{
Name: "drop-hostkeys-message",
Value: false,
@ -299,6 +305,7 @@ func main() {
log.Errorf("plugin %v recv logs error: %v", p.Name, err)
}
}()
plugins = append(plugins, p)
}

2
crypto

@ -1 +1 @@
Subproject commit 2a4b9c2448bc0257714a950e5d61a55d3633eb65
Subproject commit ff1e56f4b7ef0bf248a7bba9c996414504ffa09a

View file

@ -1 +1 @@
sshpiper banner from upstream test
sshpiper banner from upstream test