docs and examples
Signed-off-by: Jeff Lindsay <progrium@gmail.com>
This commit is contained in:
parent
dd02304d04
commit
7f8cc2a19a
39
doc.go
Normal file
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
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"))
|
||||
}
|
12
options.go
12
options.go
@ -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 {
|
||||
|
40
server.go
40
server.go
@ -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)
|
||||
}
|
||||
|
30
session.go
30
session.go
@ -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
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
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user