open-vault/builtin/logical/ssh/util.go

215 lines
6 KiB
Go

package ssh
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"net"
"strings"
"time"
"github.com/hashicorp/vault/logical"
commssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh"
)
// Creates a SSH session object which can be used to run commands
// in the target machine. The session will use public key authentication
// method with port 22.
func createSSHPublicKeysSession(username, ipAddr, port, hostKey string) (*ssh.Session, error) {
if username == "" {
return nil, fmt.Errorf("missing username")
}
if ipAddr == "" {
return nil, fmt.Errorf("missing ip address")
}
if hostKey == "" {
return nil, fmt.Errorf("missing host key")
}
signer, err := ssh.ParsePrivateKey([]byte(hostKey))
if err != nil {
return nil, fmt.Errorf("parsing Private Key failed: %s", err)
}
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", ipAddr, port), config)
if err != nil {
return nil, err
}
if client == nil {
return nil, fmt.Errorf("invalid client object: %s", err)
}
session, err := client.NewSession()
if err != nil {
return nil, err
}
return session, nil
}
// Creates a new RSA key pair with key length of 2048.
// The private key will be of pem format and the public key will be
// of OpenSSH format.
func generateRSAKeys(keyBits int) (publicKeyRsa string, privateKeyRsa string, err error) {
privateKey, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
return "", "", fmt.Errorf("error generating RSA key-pair: %s", err)
}
privateKeyRsa = string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}))
sshPublicKey, err := ssh.NewPublicKey(privateKey.Public())
if err != nil {
return "", "", fmt.Errorf("error generating RSA key-pair: %s", err)
}
publicKeyRsa = "ssh-rsa " + base64.StdEncoding.EncodeToString(sshPublicKey.Marshal())
return
}
// Concatenates the public present in that target machine's home
// folder to ~/.ssh/authorized_keys file
func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostkey string) error {
session, err := createSSHPublicKeysSession(adminUser, ip, port, hostkey)
if err != nil {
return fmt.Errorf("unable to create SSH Session using public keys: %s", err)
}
if session == nil {
return fmt.Errorf("invalid session object")
}
defer session.Close()
authKeysFileName := fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)
scriptFileName := fmt.Sprintf("%s.sh", publicKeyFileName)
// Give execute permissions to install script, run and delete it.
chmodCmd := fmt.Sprintf("chmod +x %s", scriptFileName)
scriptCmd := fmt.Sprintf("./%s install %s %s", scriptFileName, publicKeyFileName, authKeysFileName)
rmCmd := fmt.Sprintf("rm -f %s", scriptFileName)
targetCmd := fmt.Sprintf("%s;%s;%s", chmodCmd, scriptCmd, rmCmd)
session.Run(targetCmd)
return nil
}
// Removes the installed public key from the authorized_keys file
// in target machine
func uninstallPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostKey string) error {
session, err := createSSHPublicKeysSession(adminUser, ip, port, hostKey)
if err != nil {
return fmt.Errorf("unable to create SSH Session using public keys: %s", err)
}
if session == nil {
return fmt.Errorf("invalid session object")
}
defer session.Close()
authKeysFileName := fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)
scriptFileName := fmt.Sprintf("%s.sh", publicKeyFileName)
// Give execute permissions to install script, run and delete it.
chmodCmd := fmt.Sprintf("chmod +x %s", scriptFileName)
scriptCmd := fmt.Sprintf("./%s uninstall %s %s", scriptFileName, publicKeyFileName, authKeysFileName)
rmCmd := fmt.Sprintf("rm -f %s", scriptFileName)
targetCmd := fmt.Sprintf("%s;%s;%s", chmodCmd, scriptCmd, rmCmd)
session.Run(targetCmd)
return nil
}
// Takes an IP address and role name and checks if the IP is part
// of CIDR blocks belonging to the role.
func roleContainsIP(s logical.Storage, roleName string, ip string) (bool, error) {
if roleName == "" {
return false, fmt.Errorf("missing role name")
}
if ip == "" {
return false, fmt.Errorf("missing ip")
}
roleEntry, err := s.Get(fmt.Sprintf("roles/%s", roleName))
if err != nil {
return false, fmt.Errorf("error retrieving role '%s'", err)
}
if roleEntry == nil {
return false, fmt.Errorf("role '%s' not found", roleName)
}
var role sshRole
if err := roleEntry.DecodeJSON(&role); err != nil {
return false, fmt.Errorf("error decoding role '%s'", roleName)
}
if matched, err := cidrContainsIP(ip, role.CIDR); err != nil {
return false, err
} else {
return matched, nil
}
}
// Returns true if the IP supplied by the user is part of the comma
// separated CIDR blocks
func cidrContainsIP(ip, cidr string) (bool, error) {
for _, item := range strings.Split(cidr, ",") {
_, cidrIPNet, err := net.ParseCIDR(item)
if err != nil {
return false, fmt.Errorf("invalid cidr entry '%s'", item)
}
if cidrIPNet.Contains(net.ParseIP(ip)) {
return true, nil
}
}
return false, nil
}
func scpUpload(username, ip, port, hostkey, fileName, fileContent string) error {
signer, err := ssh.ParsePrivateKey([]byte(hostkey))
clientConfig := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
connfunc := func() (net.Conn, error) {
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", ip, port), 15*time.Second)
if err != nil {
return nil, err
}
if tcpConn, ok := c.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(5 * time.Second)
}
return c, nil
}
config := &commssh.Config{
SSHConfig: clientConfig,
Connection: connfunc,
Pty: false,
DisableAgent: true,
}
comm, err := commssh.New(fmt.Sprintf("%s:%s", ip, port), config)
if err != nil {
return fmt.Errorf("error connecting to target: %s", err)
}
comm.Upload(fileName, bytes.NewBufferString(fileContent), nil)
return nil
}