@@ -45,6 +45,7 @@ import (
4545
4646 "github.com/dsnet/golib/jsonfmt"
4747 "golang.org/x/crypto/ssh"
48+ "golang.org/x/crypto/ssh/agent"
4849 "golang.org/x/crypto/ssh/knownhosts"
4950)
5051
@@ -56,6 +57,8 @@ type TunnelConfig struct {
5657 // If the path is empty, then the server will output to os.Stderr.
5758 LogFile string `json:",omitempty"`
5859
60+ SshAgentSocket string `json:",omitempty"`
61+
5962 // KeyFiles is a list of SSH private key files.
6063 KeyFiles []string
6164
@@ -116,6 +119,21 @@ type KeepAliveConfig struct {
116119 CountMax uint
117120}
118121
122+ func setupSshAgent (socket string ) ssh.AuthMethod {
123+ if len (socket ) == 0 {
124+ return nil
125+ }
126+
127+ conn , err := net .Dial ("unix" , socket )
128+ if err != nil {
129+ log .Printf ("Failed to open SSH_AUTH_SOCK %s: %v\n " , socket , err )
130+ return nil
131+ }
132+
133+ agentClient := agent .NewClient (conn )
134+ return ssh .PublicKeysCallback (agentClient .Signers )
135+ }
136+
119137func loadConfig (conf string ) (tunns []tunnel , logger * log.Logger , closer func () error ) {
120138 var logBuf bytes.Buffer
121139 logger = log .New (io .MultiWriter (os .Stderr , & logBuf ), "" , log .Ldate | log .Ltime | log .Lshortfile )
@@ -137,6 +155,10 @@ func loadConfig(conf string) (tunns []tunnel, logger *log.Logger, closer func()
137155 if err := json .Unmarshal (c , & config ); err != nil {
138156 logger .Fatalf ("unable to decode config: %v" , err )
139157 }
158+ if config .SshAgentSocket == "" {
159+ // ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
160+ config .SshAgentSocket = os .Getenv ("SSH_AUTH_SOCK" )
161+ }
140162 for _ , t := range config .Tunnels {
141163 if config .KeepAlive == nil && t .KeepAlive == nil {
142164 config .KeepAlive = & KeepAliveConfig {Interval : 30 , CountMax : 2 }
@@ -171,11 +193,10 @@ func loadConfig(conf string) (tunns []tunnel, logger *log.Logger, closer func()
171193 closer = f .Close
172194 }
173195
196+ var auth []ssh.AuthMethod
197+
174198 // Parse all of the private keys.
175199 var keys []ssh.Signer
176- if len (config .KeyFiles ) == 0 {
177- logger .Fatal ("no private keys specified" )
178- }
179200 for _ , kf := range config .KeyFiles {
180201 b , err := ioutil .ReadFile (kf )
181202 if err != nil {
@@ -187,7 +208,19 @@ func loadConfig(conf string) (tunns []tunnel, logger *log.Logger, closer func()
187208 }
188209 keys = append (keys , k )
189210 }
190- auth := []ssh.AuthMethod {ssh .PublicKeys (keys ... )}
211+ if len (keys ) > 0 {
212+ auth = append (auth , ssh .PublicKeys (keys ... ))
213+ }
214+
215+ // Setup ssh-agent(1)
216+ agent := setupSshAgent (config .SshAgentSocket )
217+ if agent != nil {
218+ auth = append (auth , agent )
219+ }
220+
221+ if len (auth ) == 0 {
222+ logger .Panic ("no private keys and ssh-agent usable" )
223+ }
191224
192225 // Parse all of the host public keys.
193226 if len (config .KnownHostFiles ) == 0 {
0 commit comments