docs and examples

Signed-off-by: Jeff Lindsay <progrium@gmail.com>
This commit is contained in:
Jeff Lindsay 2016-11-29 17:23:42 -06:00
parent dd02304d04
commit 7f8cc2a19a
7 changed files with 183 additions and 15 deletions

39
doc.go Normal file

@ -0,0 +1,39 @@
/*
Package ssh wraps the crypto/ssh package with a higher-level API for building
SSH servers. The goal of the API was to make it as simple as using net/http, so
the API is very similar.
You should be able to build any SSH server using only this package, which wraps
relevant types and some functions from crypto/ssh. However, you still need to
use crypto/ssh for building SSH clients.
ListenAndServe starts an SSH server with a given address, handler, and options. The
handler is usually nil, which means to use DefaultHandler. Handle sets DefaultHandler:
ssh.Handle(func(s ssh.Session) {
io.WriteString(s, "Hello world\n")
})
log.Fatal(ssh.ListenAndServe(":2222", nil))
If you don't specify a host key, it will generate one every time. This is convenient
except you'll have to deal with clients being confused that the host key is different.
It's a better idea to generate or point to an existing key on your system:
log.Fatal(ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("/Users/progrium/.ssh/id_rsa")))
Although all options have functional option helpers, another way to control the
server's behavior is by creating a custom Server:
s := &ssh.Server{
Addr: ":2222",
Handler: sessionHandler,
PublicKeyHandler: authHandler,
}
s.AddHostKey(hostKeySigner)
log.Fatal(s.ListenAndServe())
*/
package ssh

40
example_test.go Normal file

@ -0,0 +1,40 @@
package ssh_test
import (
"io"
"io/ioutil"
"github.com/gliderlabs/ssh"
)
func ExampleListenAndServe() {
ssh.ListenAndServe(":2222", func(s ssh.Session) {
io.WriteString(s, "Hello world\n")
})
}
func ExamplePasswordAuth() {
ssh.ListenAndServe(":2222", nil,
ssh.PasswordAuth(func(user, pass string) bool {
return pass == "secret"
}),
)
}
func ExampleNoPty() {
ssh.ListenAndServe(":2222", nil, ssh.NoPty())
}
func ExamplePublicKeyAuth() {
ssh.ListenAndServe(":2222", nil,
ssh.PublicKeyAuth(func(user string, key ssh.PublicKey) bool {
data, _ := ioutil.ReadFile("/path/to/allowed/key.pub")
allowed, _, _, _, _ := ssh.ParseAuthorizedKey(data)
return ssh.KeysEqual(key, allowed)
}),
)
}
func ExampleHostKeyFile() {
ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("/path/to/host/key"))
}

@ -2,6 +2,7 @@ package ssh
import "io/ioutil"
// PasswordAuth returns a functional option that sets PasswordHandler on the server.
func PasswordAuth(fn PasswordHandler) Option {
return func(srv *Server) error {
srv.PasswordHandler = fn
@ -9,6 +10,7 @@ func PasswordAuth(fn PasswordHandler) Option {
}
}
// PublicKeyAuth returns a functional option that sets PublicKeyHandler on the server.
func PublicKeyAuth(fn PublicKeyHandler) Option {
return func(srv *Server) error {
srv.PublicKeyHandler = fn
@ -16,6 +18,8 @@ func PublicKeyAuth(fn PublicKeyHandler) Option {
}
}
// HostKeyFile returns a functional option that adds HostSigners to the server
// from a PEM file at filepath.
func HostKeyFile(filepath string) Option {
return func(srv *Server) error {
pemBytes, err := ioutil.ReadFile(filepath)
@ -27,12 +31,14 @@ func HostKeyFile(filepath string) Option {
if err != nil {
return err
}
srv.HostSigners = append(srv.HostSigners, signer)
srv.AddHostKey(signer)
}
return nil
}
}
// HostKeyPEM returns a functional option that adds HostSigners to the server
// from a PEM file as bytes.
func HostKeyPEM(bytes []byte) Option {
return func(srv *Server) error {
for _, block := range decodePemBlocks(bytes) {
@ -40,12 +46,14 @@ func HostKeyPEM(bytes []byte) Option {
if err != nil {
return err
}
srv.HostSigners = append(srv.HostSigners, signer)
srv.AddHostKey(signer)
}
return nil
}
}
// NoPty returns a functional option that sets PtyCallback to return false,
// denying PTY requests.
func NoPty() Option {
return func(srv *Server) error {
srv.PtyCallback = func(user string, permissions *Permissions) bool {

@ -8,14 +8,18 @@ import (
gossh "golang.org/x/crypto/ssh"
)
// Server defines parameters for running an SSH server. The zero value for
// Server is a valid configuration. When both PasswordHandler and
// PublicKeyHandler are nil, no client authentication is performed.
type Server struct {
Addr string
Handler Handler
HostSigners []Signer
PasswordHandler PasswordHandler
PublicKeyHandler PublicKeyHandler
PermissionsCallback PermissionsCallback
PtyCallback PtyCallback
Addr string // TCP address to listen on, ":22" if empty
Handler Handler // handler to invoke, ssh.DefaultHandler if nil
HostSigners []Signer // private keys for the host key, must have at least one
PasswordHandler PasswordHandler // password authentication handler
PublicKeyHandler PublicKeyHandler // public key authentication handler
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
PermissionsCallback PermissionsCallback // optional callback for setting up permissions
}
func (srv *Server) makeConfig() (*gossh.ServerConfig, error) {
@ -63,10 +67,16 @@ func (srv *Server) makeConfig() (*gossh.ServerConfig, error) {
return config, nil
}
// Handle sets the Handler for the server.
func (srv *Server) Handle(fn Handler) {
srv.Handler = fn
}
// Serve accepts incoming connections on the Listener l, creating a new
// connection goroutine for each. The connection goroutines read requests and then
// calls srv.Handler to handle sessions.
//
// Serve always returns a non-nil error.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
config, err := srv.makeConfig()
@ -74,7 +84,7 @@ func (srv *Server) Serve(l net.Listener) error {
return err
}
if srv.Handler == nil {
srv.Handler = defaultHandler
srv.Handler = DefaultHandler
}
var tempDelay time.Duration
for {
@ -89,7 +99,6 @@ func (srv *Server) Serve(l net.Listener) error {
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
//srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
@ -134,6 +143,9 @@ func (srv *Server) newSession(conn *gossh.ServerConn, ch gossh.Channel) *session
return sess
}
// ListenAndServe listens on the TCP network address srv.Addr and then calls
// Serve to handle incoming connections. If srv.Addr is blank, ":22" is used.
// ListenAndServe always returns a non-nil error.
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
@ -146,6 +158,16 @@ func (srv *Server) ListenAndServe() error {
return srv.Serve(ln)
}
// AddHostKey adds a private key as a host key. If an existing host key exists
// with the same algorithm, it is overwritten. Each server config must have at
// least one host key.
func (srv *Server) AddHostKey(key Signer) {
// these are later added via AddHostKey on ServerConfig, which performs the
// check for one of every algorithm.
srv.HostSigners = append(srv.HostSigners, signer)
}
// SetOption runs a functional option against the server.
func (srv *Server) SetOption(option Option) error {
return option(srv)
}

@ -9,16 +9,44 @@ import (
gossh "golang.org/x/crypto/ssh"
)
// Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from
// cypto/ssh.
//
// When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments.
//
// TODO: Signals
type Session interface {
gossh.Channel
// User returns the username used when establishing the SSH connection.
User() string
// RemoteAddr returns the net.Addr of the client side of the connection.
RemoteAddr() net.Addr
// Environ returns a copy of strings representing the environment set by the
// user for this session, in the form "key=value".
Environ() []string
// Exit sends an exit status and then closes the session.
Exit(code int) error
// Command returns a shell parsed slice of arguments that were provided by the
// user. Shell parsing splits the command string according to POSIX shell rules,
// which considers quoting not just whitespace.
Command() []string
//Signals(c chan<- Signal)
// PublicKey returns the PublicKey used to authenticate. If a public key was not
// used it will return nil.
PublicKey() PublicKey
// Pty returns PTY information, a channel of window size changes, and a boolean
// of whether or not a PTY was created for this session.
Pty() (Pty, <-chan Window, bool)
// TODO: Signals(c chan<- Signal)
}
type session struct {

26
ssh.go

@ -24,26 +24,42 @@ const (
SIGUSR2 Signal = "USR2"
)
var defaultHandler Handler
// DefaultHandler is the default Handler used by Serve.
var DefaultHandler Handler
// Option is a functional option handler for Server.
type Option func(*Server) error
// Handler is a callback for handling established SSH sessions.
type Handler func(Session)
// PublicKeyHandler is a callback for performing public key authentication.
type PublicKeyHandler func(user string, key PublicKey) bool
// PasswordHandler is a callback for performing password authentication.
type PasswordHandler func(user, password string) bool
// PermissionsCallback is a hook for setting up user permissions.
type PermissionsCallback func(user string, permissions *Permissions) error
// PtyCallback is a hook for allowing PTY sessions.
type PtyCallback func(user string, permissions *Permissions) bool
// Window represents the size of a PTY window.
type Window struct {
Width int
Height int
}
// Pty represents PTY configuration.
type Pty struct {
Window Window
}
// Serve accepts incoming SSH connections on the listener l, creating a new
// connection goroutine for each. The connection goroutines read requests and
// then calls handler to handle sessions. Handler is typically nil, in which
// case the DefaultHandler is used.
func Serve(l net.Listener, handler Handler, options ...Option) error {
srv := &Server{Handler: handler}
for _, option := range options {
@ -54,6 +70,9 @@ func Serve(l net.Listener, handler Handler, options ...Option) error {
return srv.Serve(l)
}
// ListenAndServe listens on the TCP network address addr and then calls Serve
// with handler to handle sessions on incoming connections. Handler is typically
// nil, in which case the DefaultHandler is used.
func ListenAndServe(addr string, handler Handler, options ...Option) error {
srv := &Server{Addr: addr, Handler: handler}
for _, option := range options {
@ -64,11 +83,12 @@ func ListenAndServe(addr string, handler Handler, options ...Option) error {
return srv.ListenAndServe()
}
// Handle registers the handler as the DefaultHandler.
func Handle(handler Handler) {
defaultHandler = handler
DefaultHandler = handler
}
// KeysEqual is constant time compare of the keys to avoid timing attacks
// KeysEqual is constant time compare of the keys to avoid timing attacks.
func KeysEqual(ak, bk PublicKey) bool {
a := ak.Marshal()
b := bk.Marshal()

11
wrap.go

@ -2,22 +2,33 @@ package ssh
import gossh "golang.org/x/crypto/ssh"
// PublicKey is an abstraction of different types of public keys.
type PublicKey interface {
gossh.PublicKey
}
// The Permissions type holds fine-grained permissions that are specific to a
// user or a specific authentication method for a user. Permissions, except for
// "source-address", must be enforced in the server application layer, after
// successful authentication. The Permissions are passed on in ServerConn so a
// server implementation can honor them.
type Permissions struct {
*gossh.Permissions
}
// A Signer can create signatures that verify against a public key.
type Signer interface {
gossh.Signer
}
// ParseAuthorizedKeys parses a public key from an authorized_keys file used in
// OpenSSH according to the sshd(8) manual page.
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
return gossh.ParseAuthorizedKey(in)
}
// ParsePublicKey parses an SSH public key formatted for use in
// the SSH wire protocol according to RFC 4253, section 6.6.
func ParsePublicKey(in []byte) (out PublicKey, err error) {
return gossh.ParsePublicKey(in)
}