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"
|
import "io/ioutil"
|
||||||
|
|
||||||
|
// PasswordAuth returns a functional option that sets PasswordHandler on the server.
|
||||||
func PasswordAuth(fn PasswordHandler) Option {
|
func PasswordAuth(fn PasswordHandler) Option {
|
||||||
return func(srv *Server) error {
|
return func(srv *Server) error {
|
||||||
srv.PasswordHandler = fn
|
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 {
|
func PublicKeyAuth(fn PublicKeyHandler) Option {
|
||||||
return func(srv *Server) error {
|
return func(srv *Server) error {
|
||||||
srv.PublicKeyHandler = fn
|
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 {
|
func HostKeyFile(filepath string) Option {
|
||||||
return func(srv *Server) error {
|
return func(srv *Server) error {
|
||||||
pemBytes, err := ioutil.ReadFile(filepath)
|
pemBytes, err := ioutil.ReadFile(filepath)
|
||||||
@ -27,12 +31,14 @@ func HostKeyFile(filepath string) Option {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srv.HostSigners = append(srv.HostSigners, signer)
|
srv.AddHostKey(signer)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HostKeyPEM returns a functional option that adds HostSigners to the server
|
||||||
|
// from a PEM file as bytes.
|
||||||
func HostKeyPEM(bytes []byte) Option {
|
func HostKeyPEM(bytes []byte) Option {
|
||||||
return func(srv *Server) error {
|
return func(srv *Server) error {
|
||||||
for _, block := range decodePemBlocks(bytes) {
|
for _, block := range decodePemBlocks(bytes) {
|
||||||
@ -40,12 +46,14 @@ func HostKeyPEM(bytes []byte) Option {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srv.HostSigners = append(srv.HostSigners, signer)
|
srv.AddHostKey(signer)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoPty returns a functional option that sets PtyCallback to return false,
|
||||||
|
// denying PTY requests.
|
||||||
func NoPty() Option {
|
func NoPty() Option {
|
||||||
return func(srv *Server) error {
|
return func(srv *Server) error {
|
||||||
srv.PtyCallback = func(user string, permissions *Permissions) bool {
|
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"
|
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 {
|
type Server struct {
|
||||||
Addr string
|
Addr string // TCP address to listen on, ":22" if empty
|
||||||
Handler Handler
|
Handler Handler // handler to invoke, ssh.DefaultHandler if nil
|
||||||
HostSigners []Signer
|
HostSigners []Signer // private keys for the host key, must have at least one
|
||||||
PasswordHandler PasswordHandler
|
|
||||||
PublicKeyHandler PublicKeyHandler
|
PasswordHandler PasswordHandler // password authentication handler
|
||||||
PermissionsCallback PermissionsCallback
|
PublicKeyHandler PublicKeyHandler // public key authentication handler
|
||||||
PtyCallback PtyCallback
|
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) {
|
func (srv *Server) makeConfig() (*gossh.ServerConfig, error) {
|
||||||
@ -63,10 +67,16 @@ func (srv *Server) makeConfig() (*gossh.ServerConfig, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle sets the Handler for the server.
|
||||||
func (srv *Server) Handle(fn Handler) {
|
func (srv *Server) Handle(fn Handler) {
|
||||||
srv.Handler = fn
|
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 {
|
func (srv *Server) Serve(l net.Listener) error {
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
config, err := srv.makeConfig()
|
config, err := srv.makeConfig()
|
||||||
@ -74,7 +84,7 @@ func (srv *Server) Serve(l net.Listener) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if srv.Handler == nil {
|
if srv.Handler == nil {
|
||||||
srv.Handler = defaultHandler
|
srv.Handler = DefaultHandler
|
||||||
}
|
}
|
||||||
var tempDelay time.Duration
|
var tempDelay time.Duration
|
||||||
for {
|
for {
|
||||||
@ -89,7 +99,6 @@ func (srv *Server) Serve(l net.Listener) error {
|
|||||||
if max := 1 * time.Second; tempDelay > max {
|
if max := 1 * time.Second; tempDelay > max {
|
||||||
tempDelay = max
|
tempDelay = max
|
||||||
}
|
}
|
||||||
//srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
|
||||||
time.Sleep(tempDelay)
|
time.Sleep(tempDelay)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -134,6 +143,9 @@ func (srv *Server) newSession(conn *gossh.ServerConn, ch gossh.Channel) *session
|
|||||||
return sess
|
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 {
|
func (srv *Server) ListenAndServe() error {
|
||||||
addr := srv.Addr
|
addr := srv.Addr
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
@ -146,6 +158,16 @@ func (srv *Server) ListenAndServe() error {
|
|||||||
return srv.Serve(ln)
|
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 {
|
func (srv *Server) SetOption(option Option) error {
|
||||||
return option(srv)
|
return option(srv)
|
||||||
}
|
}
|
||||||
|
30
session.go
30
session.go
@ -9,16 +9,44 @@ import (
|
|||||||
gossh "golang.org/x/crypto/ssh"
|
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 {
|
type Session interface {
|
||||||
gossh.Channel
|
gossh.Channel
|
||||||
|
|
||||||
|
// User returns the username used when establishing the SSH connection.
|
||||||
User() string
|
User() string
|
||||||
|
|
||||||
|
// RemoteAddr returns the net.Addr of the client side of the connection.
|
||||||
RemoteAddr() net.Addr
|
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
|
Environ() []string
|
||||||
|
|
||||||
|
// Exit sends an exit status and then closes the session.
|
||||||
Exit(code int) error
|
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
|
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
|
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)
|
Pty() (Pty, <-chan Window, bool)
|
||||||
|
|
||||||
|
// TODO: Signals(c chan<- Signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
|
26
ssh.go
26
ssh.go
@ -24,26 +24,42 @@ const (
|
|||||||
SIGUSR2 Signal = "USR2"
|
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
|
type Option func(*Server) error
|
||||||
|
|
||||||
|
// Handler is a callback for handling established SSH sessions.
|
||||||
type Handler func(Session)
|
type Handler func(Session)
|
||||||
|
|
||||||
|
// PublicKeyHandler is a callback for performing public key authentication.
|
||||||
type PublicKeyHandler func(user string, key PublicKey) bool
|
type PublicKeyHandler func(user string, key PublicKey) bool
|
||||||
|
|
||||||
|
// PasswordHandler is a callback for performing password authentication.
|
||||||
type PasswordHandler func(user, password string) bool
|
type PasswordHandler func(user, password string) bool
|
||||||
|
|
||||||
|
// PermissionsCallback is a hook for setting up user permissions.
|
||||||
type PermissionsCallback func(user string, permissions *Permissions) error
|
type PermissionsCallback func(user string, permissions *Permissions) error
|
||||||
|
|
||||||
|
// PtyCallback is a hook for allowing PTY sessions.
|
||||||
type PtyCallback func(user string, permissions *Permissions) bool
|
type PtyCallback func(user string, permissions *Permissions) bool
|
||||||
|
|
||||||
|
// Window represents the size of a PTY window.
|
||||||
type Window struct {
|
type Window struct {
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pty represents PTY configuration.
|
||||||
type Pty struct {
|
type Pty struct {
|
||||||
Window Window
|
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 {
|
func Serve(l net.Listener, handler Handler, options ...Option) error {
|
||||||
srv := &Server{Handler: handler}
|
srv := &Server{Handler: handler}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
@ -54,6 +70,9 @@ func Serve(l net.Listener, handler Handler, options ...Option) error {
|
|||||||
return srv.Serve(l)
|
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 {
|
func ListenAndServe(addr string, handler Handler, options ...Option) error {
|
||||||
srv := &Server{Addr: addr, Handler: handler}
|
srv := &Server{Addr: addr, Handler: handler}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
@ -64,11 +83,12 @@ func ListenAndServe(addr string, handler Handler, options ...Option) error {
|
|||||||
return srv.ListenAndServe()
|
return srv.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle registers the handler as the DefaultHandler.
|
||||||
func Handle(handler Handler) {
|
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 {
|
func KeysEqual(ak, bk PublicKey) bool {
|
||||||
a := ak.Marshal()
|
a := ak.Marshal()
|
||||||
b := bk.Marshal()
|
b := bk.Marshal()
|
||||||
|
11
wrap.go
11
wrap.go
@ -2,22 +2,33 @@ package ssh
|
|||||||
|
|
||||||
import gossh "golang.org/x/crypto/ssh"
|
import gossh "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
// PublicKey is an abstraction of different types of public keys.
|
||||||
type PublicKey interface {
|
type PublicKey interface {
|
||||||
gossh.PublicKey
|
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 {
|
type Permissions struct {
|
||||||
*gossh.Permissions
|
*gossh.Permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Signer can create signatures that verify against a public key.
|
||||||
type Signer interface {
|
type Signer interface {
|
||||||
gossh.Signer
|
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) {
|
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
||||||
return gossh.ParseAuthorizedKey(in)
|
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) {
|
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
||||||
return gossh.ParsePublicKey(in)
|
return gossh.ParsePublicKey(in)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user