Documentation ¶
Overview ¶
Package ssh is simplified encapsulation of the standard x/crypto/ssh package with reasonable defaults and multi-client controller that can coordinate operations on multiple targets.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultPort = 22
DefaultPort for SSH connetions.
var DefaultTCPTimeout = 300 * time.Second
DefaultTCPTimeout is the default number of seconds to wait to complete a TCP connection.
Functions ¶
This section is empty.
Types ¶
type AllUnavailable ¶ added in v0.3.0
type AllUnavailable struct{}
func (AllUnavailable) Error ¶ added in v0.3.0
func (AllUnavailable) Error() string
type Client ¶ added in v0.3.0
type Client struct { // Host contains the host name or IP address along with any // authorization credentials and other data that would normally be // contained in authorized_hosts. Host *Host // Port is the port the ssh server is listening on on target server. // If unset DefaultPort is used. Port int // User contains the name of the user on target server and contains // the PEM private key authentication data as well. User *User // Timeout is the default number of seconds to wait to complete a TCP // connection. If unset DefaultTCPTimeout is used. Timeout time.Duration // Comment allows information comments about a specific client // connection to be persisted with the configuration data. Comment string // contains filtered or unexported fields }
Client encapsulates an internal ssh.Client and associates a single user, host, and port number to target for specific ssh server connection adding a Connect method which implicitly dials up a connection setting Connected() and internally caching the client as SSHClient(). Client may be safely marshaled to/from YAML/JSON directly.
Example (From_YAMLJSON) ¶
var err error // YAML references are supported. yml := []byte(` immauser: &auser name: someuser key: somethingsecret host: addr: host1 user: *auser # timeout and port set to defaults when first used if zero `) client := new(ssh.Client) err = yaml.Unmarshal(yml, client) if err != nil { fmt.Println(err) } fmt.Println(client.User.Name) fmt.Println(client.Host.Addr) fmt.Println(client.Port) // 0 fmt.Println(client.Addr()) // port added ... fmt.Println(client.Port) // but not set
Output: someuser host1 0 host1:22 0
func (Client) Addr ¶ added in v0.3.0
Addr returns network address suitable for use in TCP/IP connection strings. If the Port and Host are zero values returns empty host, colon, and DefaultPort (without setting it).
func (*Client) Connect ¶ added in v0.3.0
Connect creates a new ssh.Client using the Client.Addr and caches it internally (see [SSHClient]). If [Client.Timeout] is zero uses ssh.DefaultTCPTimeout. If [Client.Port] is zero uses ssh.DefaultPort. Always reinitializes a new connection even if [Connected] is true. Also see User.Signer and Host.KeyCallback. If an attempted connection fails sets Client.Connected to false and assigns and returns [LastError].
func (*Client) Connected ¶ added in v0.3.0
Connected returns the last connection state of the internal SSH client. This is set to true on Connect. This does not guarantee that the current connection is still valid, just the last attempt.
func (Client) Dest ¶ added in v0.3.0
Dest returns the Addr with the [User.Name] prepended with an at (@) sign (if assigned).
func (*Client) LastError ¶ added in v0.3.0
LastError returns the last error (if any) from an attempt to Connect. When set Connected is guaranteed to return false.
func (*Client) Run ¶ added in v0.3.0
Run sends the command with optional standard input to the currently open client SSH target as a new ssh.Session. If the SSH connection has not yet been established (c.Connected is false) [Connect] is called to establish a new client connection. Run returns an error if one is generated by the ssh/Session.Run call or if a new session could not be created (including attempting a new session on a timed-out connection or one that has been closed for any other reason.) It is the responsibility of the called to respond to such errors according to controller policy and associated method calls.
Example ¶
// Change YAML data and remove one / from Output // to test locally. (But never commit actual keys.) yml := []byte(` user: name: user key: | -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQAAAJDswLYs7MC2 LAAAAAtzc2gtZWQyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQ AAAEDWFaCmeeFjBMAzJvtf6z24ai1dHf2FSUmuHrONv/5K6XT9d1zfQk0nH4fVu+z2hns8 4gGL9jJnhQF9D+gTJ985AAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- host: addr: localhost auth: | |1|r3meMBTG9TZiPoVHg1n+o1N1xJk=|9I891Skl7BcqG/vaT6wXxt6bZUk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDqzw7+sN4aVOQqgTA5tC9pN/+M0KOcib3lRAGQ+MSKk/4MbdJY2REavrwRetreaIZTkZx4ykTAJ3CeCK45IzsY= `) client := new(ssh.Client) err := yaml.Unmarshal(yml, client) if err != nil { fmt.Println(err) } // basic command (no stdin) stdout, stderr, err := client.Run(`printf hello`, ``) fmt.Println(`-----`) fmt.Println(stdout) fmt.Println(stderr) // with stdin stdout, stderr, _ = client.Run(`cat`, `i'm a cat`) fmt.Println(`-----`) fmt.Println(stdout) fmt.Println(stderr) // with stderr stdout, stderr, _ = client.Run(`ls notafile`, ``) fmt.Println(`-----`) fmt.Println(stdout) fmt.Println(stderr)
Output: ----- hello ----- i'm a cat ----- ls: cannot access 'notafile': No such file or directory
type Controller ¶ added in v0.3.0
type Controller struct {
Clients []*Client
}
Controller is responsible for coordinating work requests destined for the target ssh servers as contained in its list of Clients. A Controller zero value (one with nil clients list) is safe to use for all methods but [Init] can be called to add clients as a convenience.
ctl := new(ssh.Controller).Init(cl1,cl2)
Example (From_YAMLJSON) ¶
// YAML references are supported. yml := []byte(` users: user: &user name: user key: | -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8gAAAJAZeyGhGXsh oQAAAAtzc2gtZWQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8g AAAEDdV9IJ3LNTiK7D0MFz7IR1Cz/VdqqH6SgOtiDz8/5073SN2HaGCU8lyGytWCcmNNv1 tUciO6BLnCUmKmcgmI7yAAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- hosts: localhost: &localhost addr: localhost auth: | randomoption ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/WBBaaNFajVHCL0+rQqWP3zhpyXo357iPUvl0GGHWrY6t42WTNJ+bk8shRq7eq8KwefZeL4YvsnekcZb8Uq+8= clients: - host: *localhost user: *user port: 2221 timeout: 5m - host: *localhost user: *user port: 2222 timeout: 5m - host: *localhost user: *user port: 2223 timeout: 5m `) ctl := new(ssh.Controller) err := yaml.Unmarshal(yml, ctl) if err != nil { fmt.Println(err) } fmt.Println(ctl.Clients[0].User.Name) fmt.Println(ctl.Clients[1].User.Name) fmt.Println(ctl.Clients[2].User.Name) fmt.Println(ctl.Clients[0].Port) fmt.Println(ctl.Clients[1].Port) fmt.Println(ctl.Clients[2].Port) fmt.Println(ctl.Clients[0].Timeout)
Output: user user user 2221 2222 2223 5m0s
func (*Controller) Connect ¶ added in v0.3.0
func (c *Controller) Connect() *Controller
Connect synchronously calls Connect on all Clients in order ensuring that all have successfully connected before returning. No attempt at error checking for successful connections is attempted but the Client.Connected and Client.LastError can be checked when needed. A reference to self is returned as convenience.
func (*Controller) Init ¶ added in v0.3.0
func (c *Controller) Init(clients ...*Client) *Controller
Init returns a pointer to a Controller with the Clients list initialized. Init can be called later to set the internal Client list to a newly created one. When called on a Controller with an existing Clients list replaces it with a new list. If no clients are passed, simply initializes the internal Clients list to an empty list.
func (*Controller) LogStatus ¶ added in v0.3.0
func (c *Controller) LogStatus()
func (*Controller) RandomClient ¶ added in v0.3.0
func (c *Controller) RandomClient() *Client
RandomClient returns a random active client from the Clients list skipping any that are not connected. Returns nil if no connected clients are available.
Example (None) ¶
package main import ( "fmt" "github.com/rwxrob/ssh" ) func main() { // idle, unconnected c1 := new(ssh.Client) c2 := new(ssh.Client) c3 := new(ssh.Client) ctl := new(ssh.Controller).Init(c1, c2, c3) client := ctl.RandomClient() fmt.Println(client) }
Output: <nil>
Example (Single_Good) ¶
// YAML references are supported. yml := []byte(` users: user: &auser name: user key: | -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQAAAJDswLYs7MC2 LAAAAAtzc2gtZWQyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQ AAAEDWFaCmeeFjBMAzJvtf6z24ai1dHf2FSUmuHrONv/5K6XT9d1zfQk0nH4fVu+z2hns8 4gGL9jJnhQF9D+gTJ985AAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- hosts: localhost: &localhost addr: localhost auth: | |1|r3meMBTG9TZiPoVHg1n+o1N1xJk=|9I891Skl7BcqG/vaT6wXxt6bZUk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDqzw7+sN4aVOQqgTA5tC9pN/+M0KOcib3lRAGQ+MSKk/4MbdJY2REavrwRetreaIZTkZx4ykTAJ3CeCK45IzsY= clients: - host: *localhost user: *auser comment: number one - host: *localhost user: *auser comment: number two - host: *localhost user: *auser comment: number three `) ctl := new(ssh.Controller) err := yaml.Unmarshal(yml, ctl) if err != nil { fmt.Println(err) } ctl.Connect() client := ctl.RandomClient() if client != nil { fmt.Println(client.Comment) }
Output:
func (*Controller) RunOnAny ¶ added in v0.3.0
func (c *Controller) RunOnAny(cmd string, stdin []byte) (stdout, stderr string, err error)
RunOnAny calls Client.Run on a random client from the [Clients] list. If error returned is of type net.OpError the Client.Connected is set to false and the next client in the [Clients] order is attempted. Then client producing the error has Client.Connect called in a separate goroutine (which, if successful, restores its Client.Connected status to true). If none of the clients are connected then an AllUnavailable error is returned.
Example ¶
yml := []byte(` pem: &ukey | -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQAAAJDswLYs7MC2 LAAAAAtzc2gtZWQyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQ AAAEDWFaCmeeFjBMAzJvtf6z24ai1dHf2FSUmuHrONv/5K6XT9d1zfQk0nH4fVu+z2hns8 4gGL9jJnhQF9D+gTJ985AAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- user1: &user1 name: user1 key: *ukey user2: &user2 name: user2 key: *ukey user3: &user3 name: user3 key: *ukey hosts: localhost: &localhost addr: localhost auth: | |1|r3meMBTG9TZiPoVHg1n+o1N1xJk=|9I891Skl7BcqG/vaT6wXxt6bZUk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDqzw7+sN4aVOQqgTA5tC9pN/+M0KOcib3lRAGQ+MSKk/4MbdJY2REavrwRetreaIZTkZx4ykTAJ3CeCK45IzsY= clients: - host: *localhost user: *user1 - host: *localhost user: *user2 - host: *localhost user: *user3 `) ctl := new(ssh.Controller) err := yaml.Unmarshal(yml, ctl) if err != nil { fmt.Println(err) } ctl.Connect() if err != nil { fmt.Println(err) } stdin, _, _ := ctl.RunOnAny(`whoami`, ``) fmt.Println(stdin)
Output:
Example (No_Clients) ¶
package main import ( "fmt" "github.com/rwxrob/ssh" ) func main() { ctl := new(ssh.Controller) _, _, err := ctl.RunOnAny(`echo hello`, ``) fmt.Println(err) }
Output: all SSH client targets are unavailable
Example (Single_Good) ¶
yml := []byte(` pem: &ukey | -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQAAAJDswLYs7MC2 LAAAAAtzc2gtZWQyNTUxOQAAACB0/Xdc30JNJx+H1bvs9oZ7POIBi/YyZ4UBfQ/oEyffOQ AAAEDWFaCmeeFjBMAzJvtf6z24ai1dHf2FSUmuHrONv/5K6XT9d1zfQk0nH4fVu+z2hns8 4gGL9jJnhQF9D+gTJ985AAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- user1: &user1 name: user1 key: *ukey user2: &user2 name: user2 key: *ukey user3: &user3 name: user3 key: *ukey hosts: localhost: &localhost addr: localhost auth: | |1|r3meMBTG9TZiPoVHg1n+o1N1xJk=|9I891Skl7BcqG/vaT6wXxt6bZUk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDqzw7+sN4aVOQqgTA5tC9pN/+M0KOcib3lRAGQ+MSKk/4MbdJY2REavrwRetreaIZTkZx4ykTAJ3CeCK45IzsY= clients: - host: *localhost user: *user1 - host: *localhost user: *user2 - host: *localhost user: *user3 `) ctl := new(ssh.Controller) err := yaml.Unmarshal(yml, ctl) if err != nil { fmt.Println(err) } // only connect one of the clients, others are bogus err = ctl.Clients[1].Connect() if err != nil { fmt.Println(err) } stdin, _, _ := ctl.RunOnAny(`whoami`, ``) fmt.Println(stdin)
Output: user2
type Host ¶ added in v0.2.0
type Host struct { // Network host name or IP address (required). Addr string // Complete line taken in the authorized_hosts format (optional). When // included triggers returning ssh.FixedHostKey(pubkey) for // KeyCallback. Auth string }
Host represents a single host on the network that is hosting a secure shell server. A Host may be safely marshaled/unmarshaled to/from JSON/YAML.
Example (From_YAMLJSON) ¶
var err error yml := []byte(` addr: host1 auth: "randomoption ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/WBBaaNFajVHCL0+rQqWP3zhpyXo357iPUvl0GGHWrY6t42WTNJ+bk8shRq7eq8KwefZeL4YvsnekcZb8Uq+8=" `) // YAML host1 := new(ssh.Host) err = yaml.Unmarshal(yml, host1) if err != nil { fmt.Println(err) } fmt.Println(host1.Addr) fmt.Println(host1.Auth) jsn := []byte(`{"addr":"host2","auth":"randomoption ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/WBBaaNFajVHCL0+rQqWP3zhpyXo357iPUvl0GGHWrY6t42WTNJ+bk8shRq7eq8KwefZeL4YvsnekcZb8Uq+8="}`) // JSON (which *is* YAML) host2 := new(ssh.Host) err = yaml.Unmarshal(jsn, host2) if err != nil { fmt.Println(err) } fmt.Println(host2.Addr) fmt.Println(host2.Auth)
Output: host1 randomoption ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/WBBaaNFajVHCL0+rQqWP3zhpyXo357iPUvl0GGHWrY6t42WTNJ+bk8shRq7eq8KwefZeL4YvsnekcZb8Uq+8= host2 randomoption ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/WBBaaNFajVHCL0+rQqWP3zhpyXo357iPUvl0GGHWrY6t42WTNJ+bk8shRq7eq8KwefZeL4YvsnekcZb8Uq+8=
func (Host) KeyCallback ¶ added in v0.3.0
func (h Host) KeyCallback() (ssh.HostKeyCallback, error)
KeyCallback returns ssh.FixedHostKey(pubkey) where pubkey is derived from the Auth string if Auth is not nil. Otherwise, returns ssh.InsecureIgnoreHostKey().
type User ¶ added in v0.2.0
type User struct { // Name of user on target system hosting SSH server. Name string // Private key in PEM format. Key string }
User represents a single SSH user on the target host authenticated by a private key. A User may be safely marshaled/unmarshaled from JSON/YAML.
Example (From_YAMLJSON) ¶
var err error yml := []byte(` name: user1 key: |- -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8gAAAJAZeyGhGXsh oQAAAAtzc2gtZWQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8g AAAEDdV9IJ3LNTiK7D0MFz7IR1Cz/VdqqH6SgOtiDz8/5073SN2HaGCU8lyGytWCcmNNv1 tUciO6BLnCUmKmcgmI7yAAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- `) // YAML user1 := new(ssh.User) err = yaml.Unmarshal(yml, user1) if err != nil { fmt.Println(err) } fmt.Println(user1.Name) fmt.Println(user1.Key) jsn := []byte(`{"name": "user2","key":"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8gAAAJAZeyGhGXsh\noQAAAAtzc2gtZWQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8g\nAAAEDdV9IJ3LNTiK7D0MFz7IR1Cz/VdqqH6SgOtiDz8/5073SN2HaGCU8lyGytWCcmNNv1\ntUciO6BLnCUmKmcgmI7yAAAACXJ3eHJvYkB0dgECAwQ=\n-----END OPENSSH PRIVATE KEY-----"}`) // JSON (which *is* YAML) user2 := new(ssh.User) err = yaml.Unmarshal(jsn, user2) if err != nil { fmt.Println(err) } fmt.Println(user2.Name) fmt.Println(user1.Key)
Output: user1 -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8gAAAJAZeyGhGXsh oQAAAAtzc2gtZWQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8g AAAEDdV9IJ3LNTiK7D0MFz7IR1Cz/VdqqH6SgOtiDz8/5073SN2HaGCU8lyGytWCcmNNv1 tUciO6BLnCUmKmcgmI7yAAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY----- user2 -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8gAAAJAZeyGhGXsh oQAAAAtzc2gtZWQyNTUxOQAAACB0jdh2hglPJchsrVgnJjTb9bVHIjugS5wlJipnIJiO8g AAAEDdV9IJ3LNTiK7D0MFz7IR1Cz/VdqqH6SgOtiDz8/5073SN2HaGCU8lyGytWCcmNNv1 tUciO6BLnCUmKmcgmI7yAAAACXJ3eHJvYkB0dgECAwQ= -----END OPENSSH PRIVATE KEY-----