github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/helper/ssh/provisioner.go (about)

     1  package ssh
     2  
     3  import (
     4  	"encoding/pem"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"time"
     9  
    10  	"golang.org/x/crypto/ssh"
    11  	"github.com/hashicorp/terraform/terraform"
    12  	"github.com/mitchellh/go-homedir"
    13  	"github.com/mitchellh/mapstructure"
    14  )
    15  
    16  const (
    17  	// DefaultUser is used if there is no default user given
    18  	DefaultUser = "root"
    19  
    20  	// DefaultPort is used if there is no port given
    21  	DefaultPort = 22
    22  
    23  	// DefaultScriptPath is used as the path to copy the file to
    24  	// for remote execution if not provided otherwise.
    25  	DefaultScriptPath = "/tmp/script.sh"
    26  
    27  	// DefaultTimeout is used if there is no timeout given
    28  	DefaultTimeout = 5 * time.Minute
    29  )
    30  
    31  // SSHConfig is decoded from the ConnInfo of the resource. These
    32  // are the only keys we look at. If a KeyFile is given, that is used
    33  // instead of a password.
    34  type SSHConfig struct {
    35  	User       string
    36  	Password   string
    37  	KeyFile    string `mapstructure:"key_file"`
    38  	Host       string
    39  	Port       int
    40  	Timeout    string
    41  	ScriptPath string        `mapstructure:"script_path"`
    42  	TimeoutVal time.Duration `mapstructure:"-"`
    43  }
    44  
    45  // VerifySSH is used to verify the ConnInfo is usable by remote-exec
    46  func VerifySSH(s *terraform.InstanceState) error {
    47  	connType := s.Ephemeral.ConnInfo["type"]
    48  	switch connType {
    49  	case "":
    50  	case "ssh":
    51  	default:
    52  		return fmt.Errorf("Connection type '%s' not supported", connType)
    53  	}
    54  	return nil
    55  }
    56  
    57  // ParseSSHConfig is used to convert the ConnInfo of the InstanceState into
    58  // a SSHConfig struct
    59  func ParseSSHConfig(s *terraform.InstanceState) (*SSHConfig, error) {
    60  	sshConf := &SSHConfig{}
    61  	decConf := &mapstructure.DecoderConfig{
    62  		WeaklyTypedInput: true,
    63  		Result:           sshConf,
    64  	}
    65  	dec, err := mapstructure.NewDecoder(decConf)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	if err := dec.Decode(s.Ephemeral.ConnInfo); err != nil {
    70  		return nil, err
    71  	}
    72  	if sshConf.User == "" {
    73  		sshConf.User = DefaultUser
    74  	}
    75  	if sshConf.Port == 0 {
    76  		sshConf.Port = DefaultPort
    77  	}
    78  	if sshConf.ScriptPath == "" {
    79  		sshConf.ScriptPath = DefaultScriptPath
    80  	}
    81  	if sshConf.Timeout != "" {
    82  		sshConf.TimeoutVal = safeDuration(sshConf.Timeout, DefaultTimeout)
    83  	} else {
    84  		sshConf.TimeoutVal = DefaultTimeout
    85  	}
    86  	return sshConf, nil
    87  }
    88  
    89  // safeDuration returns either the parsed duration or a default value
    90  func safeDuration(dur string, defaultDur time.Duration) time.Duration {
    91  	d, err := time.ParseDuration(dur)
    92  	if err != nil {
    93  		log.Printf("Invalid duration '%s', using default of %s", dur, defaultDur)
    94  		return defaultDur
    95  	}
    96  	return d
    97  }
    98  
    99  // PrepareConfig is used to turn the *SSHConfig provided into a
   100  // usable *Config for client initialization.
   101  func PrepareConfig(conf *SSHConfig) (*Config, error) {
   102  	sshConf := &ssh.ClientConfig{
   103  		User: conf.User,
   104  	}
   105  	if conf.KeyFile != "" {
   106  		fullPath, err := homedir.Expand(conf.KeyFile)
   107  		if err != nil {
   108  			return nil, fmt.Errorf("Failed to expand home directory: %v", err)
   109  		}
   110  		key, err := ioutil.ReadFile(fullPath)
   111  		if err != nil {
   112  			return nil, fmt.Errorf("Failed to read key file '%s': %v", conf.KeyFile, err)
   113  		}
   114  
   115  		// We parse the private key on our own first so that we can
   116  		// show a nicer error if the private key has a password.
   117  		block, _ := pem.Decode(key)
   118  		if block == nil {
   119  			return nil, fmt.Errorf(
   120  				"Failed to read key '%s': no key found", conf.KeyFile)
   121  		}
   122  		if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
   123  			return nil, fmt.Errorf(
   124  				"Failed to read key '%s': password protected keys are\n"+
   125  					"not supported. Please decrypt the key prior to use.", conf.KeyFile)
   126  		}
   127  
   128  		signer, err := ssh.ParsePrivateKey(key)
   129  		if err != nil {
   130  			return nil, fmt.Errorf("Failed to parse key file '%s': %v", conf.KeyFile, err)
   131  		}
   132  
   133  		sshConf.Auth = append(sshConf.Auth, ssh.PublicKeys(signer))
   134  	}
   135  	if conf.Password != "" {
   136  		sshConf.Auth = append(sshConf.Auth,
   137  			ssh.Password(conf.Password))
   138  		sshConf.Auth = append(sshConf.Auth,
   139  			ssh.KeyboardInteractive(PasswordKeyboardInteractive(conf.Password)))
   140  	}
   141  	host := fmt.Sprintf("%s:%d", conf.Host, conf.Port)
   142  	config := &Config{
   143  		SSHConfig:  sshConf,
   144  		Connection: ConnectFunc("tcp", host),
   145  	}
   146  	return config, nil
   147  }