feat: Add username-router plugin for SSHPiper to route connections based on username (#599)

* feat: Add username-router plugin for SSHPiper to route connections based on username

* feat: Add username-router plugin to GoReleaser configuration
This commit is contained in:
Boshi Lian 2025-05-25 04:20:21 -07:00 committed by GitHub
parent 706a36b40e
commit f56c4b67e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 87 additions and 0 deletions

View file

@ -117,6 +117,20 @@ builds:
binary: plugins/failtoban
tags:
- full
- id: plugin_username_router
env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
main: ./plugin/username-router
binary: plugins/username-router
tags:
- full
archives:
- formats: ['tar.gz']
@ -141,6 +155,7 @@ archives:
- plugin_failtoban
- plugin_docker
- plugin_kubernetes
- plugin_username_router
dockers:
- image_templates:
- "farmer1992/sshpiperd:v{{ .Version }}-amd64"
@ -248,6 +263,7 @@ snapcrafts:
- plugin_yaml
- plugin_fixed
- plugin_failtoban
- plugin_username_router
name: sshpiperd
name_template: "sshpiperd_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
summary: The missing reverse proxy for ssh scp

View file

@ -90,6 +90,7 @@ Plugin list
* [kubernetes](plugin/kubernetes/) 🔀: manage pipes via Kubernetes CRD.
* [azdevicecode](https://github.com/tg123/sshpiper-plugins/tree/main/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 targeting the dummy sshd server
* [username-router](plugin/username-router/) 🔀: route based on username, the username format is `target+username`, where `target` is the target host and `username` is the username to use for that target.
* [simplemath](plugin/simplemath/) 🔒: ask for very simple math question before login, demo purpose
* [githubapp](https://github.com/tg123/sshpiper-gh) 🔀: login ssh with your github account
* [restful](https://github.com/11notes/docker-sshpiper) by [@11notes](https://github.com/11notes) 🔀🔒: The rest plugin for sshpiperd is a simple plugin that allows you to use a restful backend for authentication and challenge.

View file

@ -0,0 +1,11 @@
# username-router plugin for sshpiper
Supports routing based on username. This plugin allows you to route connections to different targets based on the username provided during the SSH connection.
The username format is `target+username`, where `target` is the target host and `username` is the username to use for that target.
`target` can be an IP address or a hostname, and it can also include a port number in the format `target:port`.
## Usage
```
sshpiperd username-router
```

View file

@ -0,0 +1,59 @@
//go:build full || e2e
package main
import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
"github.com/tg123/sshpiper/libplugin"
"github.com/urfave/cli/v2"
)
func parseTargetUser(raw string) (target string, username string, err error) {
// Expect format: [target:port]+user
parts := strings.SplitN(raw, "+", 2)
if len(parts) != 2 {
err = fmt.Errorf("invalid format (expected target:port+user)")
return
}
target = parts[0]
username = parts[1]
return
}
func main() {
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
Name: "username-router",
Usage: "routing based on target inside username, format: 'target:port+realuser@sshpiper-host'",
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
return &libplugin.SshPiperPluginConfig{
PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) {
address, user, err := parseTargetUser(conn.User())
if err != nil {
return nil, fmt.Errorf("invalid username format %q: %w", conn.User(), err)
}
host, port, err := libplugin.SplitHostPortForSSH(address)
if err != nil {
return nil, fmt.Errorf("invalid target address %q: %w", address, err)
}
log.Info("routing to address ", address, " with user ", user)
return &libplugin.Upstream{
UserName: user,
Host: host,
Port: int32(port),
IgnoreHostKey: true,
Auth: libplugin.CreatePasswordAuth(password),
}, nil
},
}, nil
},
})
}