github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/builder/openstack/ssh.go (about)

     1  package openstack
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/gophercloud/gophercloud"
    12  	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
    13  	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
    14  	packerssh "github.com/hashicorp/packer/communicator/ssh"
    15  	"github.com/hashicorp/packer/helper/multistep"
    16  	"golang.org/x/crypto/ssh"
    17  	"golang.org/x/crypto/ssh/agent"
    18  )
    19  
    20  // CommHost looks up the host for the communicator.
    21  func CommHost(
    22  	client *gophercloud.ServiceClient,
    23  	sshinterface string,
    24  	sshipversion string) func(multistep.StateBag) (string, error) {
    25  	return func(state multistep.StateBag) (string, error) {
    26  		s := state.Get("server").(*servers.Server)
    27  
    28  		// If we have a specific interface, try that
    29  		if sshinterface != "" {
    30  			if addr := sshAddrFromPool(s, sshinterface, sshipversion); addr != "" {
    31  				log.Printf("[DEBUG] Using IP address %s from specified interface %s to connect", addr, sshinterface)
    32  				return addr, nil
    33  			}
    34  		}
    35  
    36  		// If we have a floating IP, use that
    37  		ip := state.Get("access_ip").(*floatingips.FloatingIP)
    38  		if ip != nil && ip.IP != "" {
    39  			log.Printf("[DEBUG] Using floating IP %s to connect", ip.IP)
    40  			return ip.IP, nil
    41  		}
    42  
    43  		if s.AccessIPv4 != "" {
    44  			log.Printf("[DEBUG] Using AccessIPv4 %s to connect", s.AccessIPv4)
    45  			return s.AccessIPv4, nil
    46  		}
    47  
    48  		// Try to get it from the requested interface
    49  		if addr := sshAddrFromPool(s, sshinterface, sshipversion); addr != "" {
    50  			log.Printf("[DEBUG] Using IP address %s to connect", addr)
    51  			return addr, nil
    52  		}
    53  
    54  		s, err := servers.Get(client, s.ID).Extract()
    55  		if err != nil {
    56  			return "", err
    57  		}
    58  
    59  		state.Put("server", s)
    60  		time.Sleep(1 * time.Second)
    61  
    62  		return "", errors.New("couldn't determine IP address for server")
    63  	}
    64  }
    65  
    66  // SSHConfig returns a function that can be used for the SSH communicator
    67  // config for connecting to the instance created over SSH using a private key
    68  // or a password.
    69  func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
    70  	return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
    71  		if useAgent {
    72  			authSock := os.Getenv("SSH_AUTH_SOCK")
    73  			if authSock == "" {
    74  				return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
    75  			}
    76  
    77  			sshAgent, err := net.Dial("unix", authSock)
    78  			if err != nil {
    79  				return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
    80  			}
    81  
    82  			return &ssh.ClientConfig{
    83  				User: username,
    84  				Auth: []ssh.AuthMethod{
    85  					ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
    86  				},
    87  				HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    88  			}, nil
    89  		}
    90  
    91  		privateKey, hasKey := state.GetOk("privateKey")
    92  
    93  		if hasKey {
    94  
    95  			signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
    96  			if err != nil {
    97  				return nil, fmt.Errorf("Error setting up SSH config: %s", err)
    98  			}
    99  
   100  			return &ssh.ClientConfig{
   101  				User: username,
   102  				Auth: []ssh.AuthMethod{
   103  					ssh.PublicKeys(signer),
   104  				},
   105  				HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   106  			}, nil
   107  
   108  		} else {
   109  
   110  			return &ssh.ClientConfig{
   111  				User:            username,
   112  				HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   113  				Auth: []ssh.AuthMethod{
   114  					ssh.Password(password),
   115  					ssh.KeyboardInteractive(
   116  						packerssh.PasswordKeyboardInteractive(password)),
   117  				}}, nil
   118  		}
   119  	}
   120  }
   121  
   122  func sshAddrFromPool(s *servers.Server, desired string, sshIPVersion string) string {
   123  	// Get all the addresses associated with this server. This
   124  	// was taken directly from Terraform.
   125  	for pool, networkAddresses := range s.Addresses {
   126  		// If we have an SSH interface specified, skip it if no match
   127  		if desired != "" && pool != desired {
   128  			log.Printf(
   129  				"[INFO] Skipping pool %s, doesn't match requested %s",
   130  				pool, desired)
   131  			continue
   132  		}
   133  
   134  		elements, ok := networkAddresses.([]interface{})
   135  		if !ok {
   136  			log.Printf(
   137  				"[ERROR] Unknown return type for address field: %#v",
   138  				networkAddresses)
   139  			continue
   140  		}
   141  
   142  		for _, element := range elements {
   143  			var addr string
   144  			address := element.(map[string]interface{})
   145  			if address["OS-EXT-IPS:type"] == "floating" {
   146  				addr = address["addr"].(string)
   147  			} else if sshIPVersion == "4" {
   148  				if address["version"].(float64) == 4 {
   149  					addr = address["addr"].(string)
   150  				}
   151  			} else if sshIPVersion == "6" {
   152  				if address["version"].(float64) == 6 {
   153  					addr = fmt.Sprintf("[%s]", address["addr"].(string))
   154  				}
   155  			} else {
   156  				if address["version"].(float64) == 6 {
   157  					addr = fmt.Sprintf("[%s]", address["addr"].(string))
   158  				} else {
   159  					addr = address["addr"].(string)
   160  				}
   161  			}
   162  
   163  			if addr != "" {
   164  				log.Printf("[DEBUG] Detected address: %s", addr)
   165  				return addr
   166  			}
   167  		}
   168  	}
   169  
   170  	return ""
   171  }