-
-
Save daluu/5096fd8466e978f49ff9f41922dbec93 to your computer and use it in GitHub Desktop.
Using golang ssh client with an encrypted private key
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"crypto/x509" | |
"encoding/pem" | |
"errors" | |
"fmt" | |
"golang.org/x/crypto/ssh" | |
"io/ioutil" | |
"net" | |
) | |
type SshClient struct { | |
Config *ssh.ClientConfig | |
Server string | |
} | |
func NewSshClient(user string, host string, port int, privateKeyPath string, privateKeyPassword string) (*SshClient, error) { | |
// read private key file | |
pemBytes, err := ioutil.ReadFile(privateKeyPath) | |
if err != nil { | |
return nil, fmt.Errorf("Reading private key file failed %v", err) | |
} | |
// create signer | |
signer, err := signerFromPem(pemBytes, []byte(privateKeyPassword)) | |
if err != nil { | |
return nil, err | |
} | |
// build SSH client config | |
config := &ssh.ClientConfig{ | |
User: user, | |
Auth: []ssh.AuthMethod{ | |
ssh.PublicKeys(signer), | |
}, | |
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { | |
// use OpenSSH's known_hosts file if you care about host validation | |
return nil | |
}, | |
} | |
client := &SshClient{ | |
Config: config, | |
Server: fmt.Sprintf("%v:%v", host, port), | |
} | |
return client, nil | |
} | |
// Opens a new SSH connection and runs the specified command | |
// Returns the combined output of stdout and stderr | |
func (s *SshClient) RunCommand(cmd string) (string, error) { | |
// open connection | |
conn, err := ssh.Dial("tcp", s.Server, s.Config) | |
if err != nil { | |
return "", fmt.Errorf("Dial to %v failed %v", s.Server, err) | |
} | |
defer conn.Close() | |
// open session | |
session, err := conn.NewSession() | |
if err != nil { | |
return "", fmt.Errorf("Create session for %v failed %v", s.Server, err) | |
} | |
defer session.Close() | |
// run command and capture stdout/stderr | |
output, err := session.CombinedOutput(cmd) | |
return fmt.Sprintf("%s", output), err | |
} | |
func signerFromPem(pemBytes []byte, password []byte) (ssh.Signer, error) { | |
// read pem block | |
err := errors.New("Pem decode failed, no key found") | |
pemBlock, _ := pem.Decode(pemBytes) | |
if pemBlock == nil { | |
return nil, err | |
} | |
// handle encrypted key | |
if x509.IsEncryptedPEMBlock(pemBlock) { | |
// decrypt PEM | |
pemBlock.Bytes, err = x509.DecryptPEMBlock(pemBlock, []byte(password)) | |
if err != nil { | |
return nil, fmt.Errorf("Decrypting PEM block failed %v", err) | |
} | |
// get RSA, EC or DSA key | |
key, err := parsePemBlock(pemBlock) | |
if err != nil { | |
return nil, err | |
} | |
// generate signer instance from key | |
signer, err := ssh.NewSignerFromKey(key) | |
if err != nil { | |
return nil, fmt.Errorf("Creating signer from encrypted key failed %v", err) | |
} | |
return signer, nil | |
} else { | |
// generate signer instance from plain key | |
signer, err := ssh.ParsePrivateKey(pemBytes) | |
if err != nil { | |
return nil, fmt.Errorf("Parsing plain private key failed %v", err) | |
} | |
return signer, nil | |
} | |
} | |
func parsePemBlock(block *pem.Block) (interface{}, error) { | |
switch block.Type { | |
case "RSA PRIVATE KEY": | |
key, err := x509.ParsePKCS1PrivateKey(block.Bytes) | |
if err != nil { | |
return nil, fmt.Errorf("Parsing PKCS private key failed %v", err) | |
} else { | |
return key, nil | |
} | |
case "EC PRIVATE KEY": | |
key, err := x509.ParseECPrivateKey(block.Bytes) | |
if err != nil { | |
return nil, fmt.Errorf("Parsing EC private key failed %v", err) | |
} else { | |
return key, nil | |
} | |
case "DSA PRIVATE KEY": | |
key, err := ssh.ParseDSAPrivateKey(block.Bytes) | |
if err != nil { | |
return nil, fmt.Errorf("Parsing DSA private key failed %v", err) | |
} else { | |
return key, nil | |
} | |
default: | |
return nil, fmt.Errorf("Parsing private key failed, unsupported key type %q", block.Type) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ssh, err := NewSshClient( | |
"some-user", | |
"some-host", | |
22, | |
"/Users/some-user/.ssh/id_rsa", | |
"pem-password") | |
if err != nil { | |
log.Printf("SSH init error %v", err) | |
} else { | |
output, err := ssh.RunCommand("ls") | |
fmt.Println(output) | |
if err != nil { | |
log.Printf("SSH run command error %v", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment