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