Skip to content

Commit 915c50e

Browse files
Cole BrumleyCole Brumley
Cole Brumley
authored and
Cole Brumley
committed
Re-added dynamic user lookup if -u flag undefined. Sadly, this means no more cross-compilation. Also added ssh-agent fallback if --key is undefined
1 parent e1181cb commit 915c50e

File tree

2 files changed

+81
-34
lines changed

2 files changed

+81
-34
lines changed

mssh.go

+78-23
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ import (
66
"github.com/codegangsta/cli"
77
"github.com/fatih/color"
88
"golang.org/x/crypto/ssh"
9+
"golang.org/x/crypto/ssh/agent"
910
"io/ioutil"
11+
"net"
1012
"os"
13+
// Use of the os/user package prevents cross-compilation
14+
"os/user" // <- https://github.com/golang/go/issues/6376
15+
"path/filepath"
1116
"strings"
1217
)
1318

@@ -16,7 +21,7 @@ func main() {
1621
app.HideVersion = true
1722
app.Name = "mssh"
1823
app.Usage = "Run SSH commands on multiple machines"
19-
app.Version = "0.0.2"
24+
app.Version = "0.0.2.1"
2025
app.Flags = []cli.Flag{
2126
cli.StringFlag{
2227
Name: "user,u",
@@ -43,7 +48,7 @@ func main() {
4348
},
4449
cli.BoolTFlag{
4550
Name: "color,c",
46-
Usage: "Print cmd output in color (true by default)",
51+
Usage: "Print cmd output in color (use -c=false to disable)",
4752
},
4853
}
4954
app.Action = defaultAction
@@ -52,41 +57,99 @@ func main() {
5257

5358
func defaultAction(c *cli.Context) {
5459
hosts := c.StringSlice("server")
60+
61+
currentUser, err := user.Current()
62+
if err != nil {
63+
log.Errorf("Could not get current user: %v", err)
64+
}
5565
u := c.String("user")
66+
// If no flag, get current username
67+
if len(u) == 0 {
68+
u = currentUser.Username
69+
if len(u) == 0 {
70+
log.Errorln("No username specified!")
71+
}
72+
}
5673

74+
// Getting keys:
75+
// --key flag overrides all
76+
// If no --key is defined, check for ssh-agent on $SSH_AUTH_SOCK
77+
// If no ssh-agent defined, look or key in ~/.ssh/id_rsa
78+
// fail if none of the above work
5779
key := c.String("key")
80+
auths := []ssh.AuthMethod{}
5881
if len(key) == 0 {
59-
// If no key provided use ~/.ssh/id_rsa
60-
key = os.Getenv("HOME") + "/.ssh/id_rsa"
82+
// If no key provided see if there's an ssh-agent running
83+
if len(os.Getenv("SSH_AUTH_SOCK")) > 0 {
84+
log.Println("Attempting to use existing ssh-agent")
85+
conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
86+
if err != nil {
87+
return
88+
}
89+
defer conn.Close()
90+
ag := agent.NewClient(conn)
91+
auths = []ssh.AuthMethod{ssh.PublicKeysCallback(ag.Signers)}
92+
} else {
93+
// Otherwise use ~/.ssh/id_rsa
94+
key = filepath.FromSlash(currentUser.HomeDir + "/.ssh/id_rsa")
95+
if !fileExists(key) {
96+
log.Errorln("Must specify a key, ~/.ssh/id_rsa does not exist and no ssh-agent is available!")
97+
cli.ShowAppHelp(c)
98+
os.Exit(2)
99+
}
100+
pemBytes, err := ioutil.ReadFile(key)
101+
if err != nil {
102+
log.Errorf("%v", err)
103+
}
104+
signer, err := ssh.ParsePrivateKey(pemBytes)
105+
if err != nil {
106+
log.Errorf("%v", err)
107+
}
108+
auths = []ssh.AuthMethod{ssh.PublicKeys(signer)}
109+
}
110+
} else {
61111
if !fileExists(key) {
62-
log.Errorln("Must specify a key, ~/.ssh/id_rsa does not exist!")
63-
cli.ShowAppHelp(c)
64-
os.Exit(2)
112+
log.Errorln("Specified key does not exist!")
113+
os.Exit(1)
114+
}
115+
pemBytes, err := ioutil.ReadFile(key)
116+
if err != nil {
117+
log.Errorf("%v", err)
118+
}
119+
signer, err := ssh.ParsePrivateKey(pemBytes)
120+
if err != nil {
121+
log.Errorf("%v", err)
65122
}
123+
auths = []ssh.AuthMethod{ssh.PublicKeys(signer)}
66124
}
67125

68-
// user and host(s) required
126+
// host(s) is required
69127
if len(hosts) == 0 {
70-
log.Errorln("At least one host is required")
128+
log.Warnln("At least one host is required")
71129
cli.ShowAppHelp(c)
72130
os.Exit(2)
73131
}
132+
133+
// At least one command is required
74134
if len(c.Args().First()) == 0 {
135+
log.Warnln("At least one command is required")
75136
cli.ShowAppHelp(c)
76137
os.Exit(2)
77138
}
139+
140+
// append list of commands to argList
78141
argList := append([]string{c.Args().First()}, c.Args().Tail()...)
79142
chLen := len(hosts) * len(argList)
80143
done := make(chan bool, chLen)
81144
for _, h := range hosts {
82145
// Hosts are handled asynchronously
83-
go func(c *cli.Context, host string, cmds []string) {
146+
go func(c *cli.Context, host string, cmds []string, auth []ssh.AuthMethod) {
84147
// Set the default port if none specified
85148
if len(strings.Split(host, ":")) == 1 {
86149
host = host + ":22"
87150
}
88151
for _, cmd := range cmds {
89-
combined, err := runRemoteCmd(u, host, key, cmd)
152+
combined, err := runRemoteCmd(u, host, auth, cmd)
90153
out := tail(string(combined), c.Int("lines"))
91154
if err != nil {
92155
pretty := prettyOutput(
@@ -104,7 +167,7 @@ func defaultAction(c *cli.Context) {
104167
}
105168
done <- true
106169
}
107-
}(c, h, argList)
170+
}(c, h, argList, auths)
108171
}
109172

110173
// Drain chan before exiting
@@ -150,18 +213,10 @@ func tail(s string, n int) string {
150213
return strings.Join(lines[len(lines)-(n):], "\n")
151214
}
152215

153-
func runRemoteCmd(user string, addr string, key string, cmd string) (out []byte, err error) {
154-
pemBytes, err := ioutil.ReadFile(key)
155-
if err != nil {
156-
return
157-
}
158-
signer, err := ssh.ParsePrivateKey(pemBytes)
159-
if err != nil {
160-
return
161-
}
216+
func runRemoteCmd(user string, addr string, auth []ssh.AuthMethod, cmd string) (out []byte, err error) {
162217
client, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
163218
User: user,
164-
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
219+
Auth: auth,
165220
})
166221
if err != nil {
167222
return
@@ -176,7 +231,7 @@ func runRemoteCmd(user string, addr string, key string, cmd string) (out []byte,
176231
return
177232
}
178233

179-
// FileExists returns a bool if os.Stat returns an IsNotExist error
234+
// fileExists returns a bool if os.Stat returns an IsNotExist error
180235
func fileExists(name string) bool {
181236
if _, err := os.Stat(name); err != nil {
182237
if os.IsNotExist(err) {

shippable.yml

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
language: go
2-
32
go:
43
- release
5-
6-
env:
7-
- CGO_ENABLED=0
8-
94
before_install:
105
- export GOPATH=$SHIPPABLE_GOPATH
11-
126
install:
13-
- go get -t -d -v ./... && go build -x -o mssh -a -installsuffix cgo ./...
14-
7+
- go get -t -d -v ./... && go build -x -o mssh ./...
158
script:
16-
- cp mssh shippable/
17-
18-
archive: true
9+
- cp mssh shippable/buildoutput/
10+
archive: true

0 commit comments

Comments
 (0)