An SSH routing library compatible with charmbracelet/wish and gliderlabs/ssh!
Extend your SSH applications by routing connections to unique handlers and middleware based on various characteristics such as the login username, or entrypoint.
Compatible with charmbracelet/wish
Utilizing an Icepop Router with Wish is super simple. Icepop routers implement
ssh.Middleware, which means that it fits right into any Wish application.
// source: ./examples/minimal-wish-demo/main.go
package main
import (
"fmt"
"github.com/charmbracelet/wish"
"github.com/gliderlabs/ssh"
"github.com/concerthall/icepop"
)
func main() {
// Registered handlers check for the username!
rtr := icepop.NewUsernameRouter()
// Our first handler!
rtr.Handle(
// the expected username
"itshotoutside",
// The handler to be used.
icepop.NewSessionHandlerFrom(func(s ssh.Session) {
wish.Println(s, "I love Ice pops!")
s.Exit(0)
s.Close()
}),
)
// Create the Wish server
s, _ := wish.NewServer(
wish.WithAddress("localhost:23234"),
wish.WithHostKeyPath(".ssh/term_info_ed25519"),
// Here's our router!
wish.WithMiddleware(rtr.AsMiddleware()),
)
// Start the server!
fmt.Println("Listening!")
if err := s.ListenAndServe(); err != nil {
panic(err) // handle how you see fit!
}
}And then SSH to the demo server!
$ ssh itshotoutside@localhost -p 23234
I love Ice pops!
Connection to localhost closed.
Unregistered routes are handled automatically!
$ ssh itscoldoutside@localhost -p 23234
this route does not exist
Connection to localhost closed.
Compatible with gliderlab/ssh
Because Wish extends the Gliderlabs SSH library, Icepop also fits nicely with the gliderlabs/ssh library.
// source: ./examples/minimal-gliderlabs-demo/main.go
package main
import (
"io"
"log"
"github.com/concerthall/icepop"
"github.com/gliderlabs/ssh"
)
func main() {
rtr := icepop.NewCommandRouter()
rtr.HandleFunc(
"favorite-flavor",
func(s ssh.Session) {
io.WriteString(s, "I like cherry!")
s.Exit(0)
})
ssh.Handle(rtr.Handler())
log.Println("starting the gliderlabs demo")
log.Fatal(ssh.ListenAndServe(":23234", nil))
}Output:
$ ssh localhost -p 23234 favorite-flavor
I like cherry!
Routers are just ssh.Handlers that perform some kind of logical routing based on some factor.
This library provides:
-
a Command Router which allows you to bind different handlers, or routes, to each registered command the user passes into their session.
-
A Username Router which allows you to bind different handlers, or routes, to each registered username the user passes into their session.
A Session Handler is an abstraction based on the ssh.Handler definition. It binds a ssh.Handler to a method ServeSSH, as you might find in the net/http package.
For compatibility, you can convert an existing ssh.Handler to a SessionHandler using icepop.NewSessionHandlerFrom. A note: the SessionHandler interface doesn't replace ssh.Handler, but simply exists to allow us to do some creative things!
Routes are components that represent a base handler along with its middleware. Routes can also be treated as ssh.Handlers by themselves.
Several annotated examples exist in the examples directory, along with those already defined above.
Take a look at our nested demo, where we take advantage of the flexibility the SessionHandler interface affords us by nesting a command router within a specific endpoint of a username router.
The source is commented throughout, and feel free to open an issue if anything is unclear.
Image Credit: "Ice cream stickers" by Gohsantosadrive - Flaticon
