Class: Vagrant::Util::SSH
- Inherits:
-
Object
- Object
- Vagrant::Util::SSH
- Extended by:
- SafePuts
- Defined in:
- lib/vagrant/util/ssh.rb
Overview
This is a class that has helpers on it for dealing with SSH. These helpers don't depend on any part of Vagrant except what is given via the parameters.
Constant Summary collapse
- LOGGER =
Log4r::Logger.new("vagrant::util::ssh")
Class Method Summary collapse
- ._raw_exec(ssh, command_options, ssh_info, opts) ⇒ Object
- ._raw_subprocess(ssh, command_options, ssh_info, opts) ⇒ Object
-
.check_key_permissions(key_path) ⇒ Object
Checks that the permissions for a private key are valid, and fixes them if possible.
-
.exec(ssh_info, opts = {}) ⇒ Object
Halts the running of this process and replaces it with a full-fledged SSH shell into a remote machine.
Methods included from SafePuts
Class Method Details
._raw_exec(ssh, command_options, ssh_info, opts) ⇒ Object
244 245 246 |
# File 'lib/vagrant/util/ssh.rb', line 244 def self._raw_exec(ssh, , ssh_info, opts) SafeExec.exec(ssh, *) end |
._raw_subprocess(ssh, command_options, ssh_info, opts) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/vagrant/util/ssh.rb', line 248 def self._raw_subprocess(ssh, , ssh_info, opts) # If we're still here, it means we're supposed to subprocess # out to ssh rather than exec it. process = ChildProcess.build(ssh, *) process.io.inherit! # Forward configured environment variables. if ssh_info[:forward_env] ssh_info[:forward_env].each do |key| process.environment[key] = ENV[key] end end process.start process.wait return process.exit_code end |
.check_key_permissions(key_path) ⇒ Object
Checks that the permissions for a private key are valid, and fixes them if possible. SSH requires that permissions on the private key are 0600 on POSIX based systems. This will make a best effort to fix these permissions if they are not properly set.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/vagrant/util/ssh.rb', line 31 def self.(key_path) # Don't do anything if we're on Windows, since Windows doesn't worry # about key permissions. return if Platform.windows? || Platform.wsl_windows_access_bypass?(key_path) LOGGER.debug("Checking key permissions: #{key_path}") stat = key_path.stat if !stat.owned? && Process.uid != 0 # The SSH key must be owned by ourselves, unless we're root raise Errors::SSHKeyBadOwner, key_path: key_path end if FileMode.from_octal(stat.mode) != "600" LOGGER.info("Attempting to correct key permissions to 0600") key_path.chmod(0600) # Re-stat the file to get the new mode, and verify it worked stat = key_path.stat if FileMode.from_octal(stat.mode) != "600" raise Errors::SSHKeyBadPermissions, key_path: key_path end end rescue Errno::EPERM # This shouldn't happen since we verify we own the file, but # it is possible in theory, so we raise an error. raise Errors::SSHKeyBadPermissions, key_path: key_path end |
.exec(ssh_info, opts = {}) ⇒ Object
Halts the running of this process and replaces it with a full-fledged SSH shell into a remote machine.
Note: This method NEVER returns. The process ends after this.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/vagrant/util/ssh.rb', line 69 def self.exec(ssh_info, opts={}) # Ensure the platform supports ssh. On Windows there are several programs which # include ssh, notably git, mingw and cygwin, but make sure ssh is in the path! # First try using the original path provided if ENV["VAGRANT_PREFER_SYSTEM_BIN"] != "0" ssh_path = Which.which("ssh", original_path: true) end # If we didn't find an ssh executable, see if we shipped one if !ssh_path ssh_path = Which.which("ssh") if ssh_path && Platform.windows? && (Platform.cygwin? || Platform.msys?) LOGGER.warn("Failed to locate native SSH executable. Using vendored version.") LOGGER.warn("If display issues are encountered, install the ssh package for your environment.") end end if !ssh_path if Platform.windows? raise Errors::SSHUnavailableWindows, host: ssh_info[:host], port: ssh_info[:port], username: ssh_info[:username], key_path: ssh_info[:private_key_path].join(", ") end raise Errors::SSHUnavailable end if Platform.windows? # On Windows, we need to detect whether SSH is actually "plink" # underneath the covers. In this case, we tell the user. r = Subprocess.execute(ssh_path) if r.stdout.include?("PuTTY Link") || r.stdout.include?("Plink: command-line connection utility") raise Errors::SSHIsPuttyLink, host: ssh_info[:host], port: ssh_info[:port], username: ssh_info[:username], key_path: ssh_info[:private_key_path].join(", ") end end # If plain mode is enabled then we don't do any authentication (we don't # set a user or an identity file) plain_mode = opts[:plain_mode] = {} [:host] = ssh_info[:host] [:port] = ssh_info[:port] [:username] = ssh_info[:username] [:private_key_path] = ssh_info[:private_key_path] log_level = ssh_info[:log_level] || "FATAL" # Command line options = [ "-p", [:port].to_s, "-o", "LogLevel=#{log_level}"] if ssh_info[:compression] += ["-o", "Compression=yes"] end if ssh_info[:dsa_authentication] += ["-o", "DSAAuthentication=yes"] end # Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the # IdentitiesOnly option. Also, we don't enable it in plain mode or if # if keys_only is false so that SSH and Net::SSH properly search our identities # and tries to do it itself. if !Platform.solaris? && !plain_mode && ssh_info[:keys_only] += ["-o", "IdentitiesOnly=yes"] end # no strict hostkey checking unless paranoid if ssh_info[:verify_host_key] == :never || !ssh_info[:verify_host_key] += [ "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"] end if !ssh_info[:disable_deprecated_algorithms] += [ "-o", "PubkeyAcceptedKeyTypes=+ssh-rsa", "-o", "HostKeyAlgorithms=+ssh-rsa", ] end # If we're not in plain mode and :private_key_path is set attach the private key path(s). if !plain_mode && [:private_key_path] [:private_key_path].each do |path| private_key_arr = [] if path.include?('%') if path.include?(' ') && Platform.windows? LOGGER.warn("Paths with spaces and % on windows is not supported and will fail to read the file") end # Use '-o' instead of '-i' because '-i' does not call # percent_expand in misc.c, but '-o' does. when passing the path, # replace '%' in the path with '%%' to escape the '%' path = path.to_s.gsub('%', '%%') private_key_arr = ["-o", "IdentityFile=\"#{path}\""] else # Pass private key file directly with '-i', which properly supports # paths with spaces on Windows guests private_key_arr = ["-i", path] end += private_key_arr end end if ssh_info[:forward_x11] # Both are required so that no warnings are shown regarding X11 += [ "-o", "ForwardX11=yes", "-o", "ForwardX11Trusted=yes"] end if ssh_info[:config] += ["-F", ssh_info[:config]] end if ssh_info[:proxy_command] += ["-o", "ProxyCommand=#{ssh_info[:proxy_command]}"] end if ssh_info[:forward_env] += ["-o", "SendEnv=#{ssh_info[:forward_env].join(" ")}"] end # Configurables -- extra_args should always be last due to the way the # ssh args parser works. e.g. if the user wants to use the -t option, # any shell command(s) she'd like to run on the remote server would # have to be the last part of the 'ssh' command: # # $ ssh localhost -t -p 2222 "cd mydirectory; bash" # # Without having extra_args be last, the user loses this ability += ["-o", "ForwardAgent=yes"] if ssh_info[:forward_agent] # Note about :extra_args # ssh_info[:extra_args] comes from a machines ssh config in a Vagrantfile, # where as opts[:extra_args] comes from running the ssh command += Array(ssh_info[:extra_args]) if ssh_info[:extra_args] .concat(opts[:extra_args]) if opts[:extra_args] # Build up the host string for connecting host_string = [:host] host_string = "#{[:username]}@#{host_string}" if !plain_mode .unshift(host_string) # On Cygwin we want to get rid of any DOS file warnings because # we really don't care since both work. ENV["nodosfilewarning"] = "1" if Platform.cygwin? # If an ssh command is defined, use that. If an ssh binary was # discovered on the path, use that. Otherwise fail to just trying `ssh` ssh = ssh_info[:ssh_command] || ssh_path || 'ssh' # Invoke SSH with all our options if !opts[:subprocess] LOGGER.info("Invoking SSH: #{ssh} #{.inspect}") _raw_exec(ssh, , ssh_info, opts) return else LOGGER.info("Executing SSH in subprocess: #{ssh} #{.inspect}") return _raw_subprocess(ssh, , ssh_info, opts) end end |