/icepop

A SSH Routing Library

Primary LanguageGoMIT LicenseMIT

Icepop

icepop

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!

Concepts & Constructs

Routers

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.

SessionHandlers

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

Routes are components that represent a base handler along with its middleware. Routes can also be treated as ssh.Handlers by themselves.

Examples

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.

Docs

The source is commented throughout, and feel free to open an issue if anything is unclear.

Go Doc

Attribution

Image Credit: "Ice cream stickers" by Gohsantosadrive - Flaticon