class Nucleon::Util::SSH
Public Class Methods
close(hostname = nil, user = nil)
click to toggle source
# File lib/core/util/ssh.rb 220 def self.close(hostname = nil, user = nil) 221 if hostname && user.nil? # Assume we entered a session id 222 if @@sessions.has_key?(hostname) 223 @@sessions[hostname].close 224 @@sessions.delete(hostname) 225 end 226 227 elsif hostname && user # Generate session id from args 228 session_id = session_id(hostname, user) 229 230 if @@sessions.has_key?(session_id) 231 @@sessions[session_id].close 232 @@sessions.delete(session_id) 233 end 234 235 else # Close all connections 236 @@sessions.keys.each do |id| 237 @@sessions[id].close 238 @@sessions.delete(id) 239 end 240 end 241 end
close_session(hostname, user)
click to toggle source
# File lib/core/util/ssh.rb 206 def self.close_session(hostname, user) 207 session_id = session_id(hostname, user) 208 209 if @@sessions.has_key?(session_id) 210 begin # Don't care about errors here 211 @@sessions[session_id].close 212 rescue 213 end 214 @@sessions.delete(session_id) 215 end 216 end
download(hostname, user, remote_path, local_path, options = {}) { |name, received, total| ... }
click to toggle source
# File lib/core/util/ssh.rb 299 def self.download(hostname, user, remote_path, local_path, options = {}) 300 config = Config.ensure(options) 301 302 require 'net/scp' 303 304 # Accepted options: 305 # * :recursive - the +remote+ parameter refers to a remote directory, which 306 # should be downloaded to a new directory named +local+ on the local 307 # machine. 308 # * :preserve - the atime and mtime of the file should be preserved. 309 # * :verbose - the process should result in verbose output on the server 310 # end (useful for debugging). 311 # 312 config.init(:recursive, true) 313 config.init(:preserve, true) 314 config.init(:verbose, true) 315 316 blocking = config.delete(:blocking, true) 317 318 session(hostname, user) do |ssh| 319 if blocking 320 ssh.scp.download!(remote_path, local_path, config.export) do |ch, name, received, total| 321 yield(name, received, total) if block_given? 322 end 323 else 324 ssh.scp.download(remote_path, local_path, config.export) 325 end 326 end 327 end
exec(hostname, user, commands) { |:output, command, data| ... }
click to toggle source
# File lib/core/util/ssh.rb 245 def self.exec(hostname, user, commands) 246 results = [] 247 248 begin 249 session(hostname, user) do |ssh| 250 Data.array(commands).each do |command| 251 command = command.flatten.join(' ') if command.is_a?(Array) 252 command = command.to_s 253 result = Shell::Result.new(command) 254 255 logger.info(">> running SSH: #{command}") 256 257 ssh.open_channel do |ssh_channel| 258 ssh_channel.exec(command) do |channel, success| 259 unless success 260 raise "Could not execute command: #{command.inspect}" 261 end 262 263 channel.on_data do |ch, data| 264 data = yield(:output, command, data) if block_given? 265 result.append_output(data) 266 end 267 268 channel.on_extended_data do |ch, type, data| 269 data = yield(:error, command, data) if block_given? 270 result.append_errors(data) 271 end 272 273 channel.on_request('exit-status') do |ch, data| 274 result.status = data.read_long 275 end 276 277 channel.on_request('exit-signal') do |ch, data| 278 result.status = 255 279 end 280 end 281 end 282 logger.warn("`#{command}` messages: #{result.errors}") if result.errors.length > 0 283 logger.warn("`#{command}` status: #{result.status}") unless result.status == 0 284 285 results << result 286 ssh.loop 287 end 288 end 289 rescue Net::SSH::HostKeyMismatch => error 290 error.remember_host! 291 sleep 0.2 292 retry 293 end 294 results 295 end
generate(options = {})
click to toggle source
# File lib/core/util/ssh.rb 30 def self.generate(options = {}) 31 config = Config.ensure(options) 32 33 private_key = config.get(:private_key, nil) 34 original_key = nil 35 key_comment = config.get(:comment, '') 36 passphrase = config.get(:passphrase, nil) 37 force = config.get(:force, false) 38 39 if private_key.nil? 40 key_type = config.get(:type, "RSA") 41 key_bits = config.get(:bits, 2048) 42 43 key_data = SSHKey.generate({ 44 :type => key_type, 45 :bits => key_bits, 46 :comment => key_comment, 47 :passphrase => passphrase 48 }) 49 is_new = true 50 51 else 52 if private_key.include?('PRIVATE KEY') 53 original_key = private_key 54 else 55 original_key = Disk.read(private_key) 56 end 57 58 if original_key 59 encrypted = original_key.include?('ENCRYPTED') 60 key_data = SSHKey.new(original_key, { 61 :comment => key_comment, 62 :passphrase => passphrase 63 }) if force || ! encrypted || passphrase 64 end 65 is_new = false 66 end 67 68 return nil unless key_data && ! key_data.ssh_public_key.empty? 69 Keypair.new(key_data, is_new, original_key, passphrase) 70 end
init_session(hostname, user, port = 22, private_key = nil, options = {})
click to toggle source
# File lib/core/util/ssh.rb 200 def self.init_session(hostname, user, port = 22, private_key = nil, options = {}) 201 session(hostname, user, port, private_key, true, options) 202 end
key_path(ssh_user = nil)
click to toggle source
# File lib/core/util/ssh.rb 13 def self.key_path(ssh_user = nil) 14 unless @@key_path 15 if ssh_user 16 home_path = "/home/#{ssh_user}" 17 else 18 home_path = ( ENV['USER'] == 'root' ? '/root' : ENV['HOME'] ) # In case we are using sudo 19 end 20 @@key_path = File.join(home_path, '.ssh') 21 22 FileUtils.mkdir(@@key_path) unless File.directory?(@@key_path) 23 end 24 @@key_path 25 end
session(hostname, user, port = 22, private_key = nil, reset = false, options = {}) { |sessions| ... }
click to toggle source
# File lib/core/util/ssh.rb 157 def self.session(hostname, user, port = 22, private_key = nil, reset = false, options = {}) 158 require 'net/ssh' 159 160 session_id = session_id(hostname, user) 161 162 @@session_lock.synchronize do 163 config = Config.ensure(options) 164 165 ssh_options = Config.new({ 166 :user_known_hosts_file => [ File.join(key_path, 'known_hosts'), File.join(key_path, 'known_hosts2') ], 167 :auth_methods => [ 'publickey', 'password' ], 168 :paranoid => :very 169 }, {}, true, false).import(Util::Data.subset(config, config.keys - [ :keypair, :key_dir, :key_name, :reset_conn ])) 170 171 if private_key 172 auth_id = [ session_id, private_key ].join('_') 173 174 if (config[:reset_conn] || ! @@auth[auth_id]) && keypair = unlock_private_key(private_key, config) 175 @@auth[auth_id] = keypair 176 end 177 config[:keypair] = @@auth[auth_id] # Reset so caller can access updated keypair 178 179 if @@auth[auth_id].is_a?(String) 180 ssh_options[:keys_only] = false 181 ssh_options[:keys] = [ @@auth[auth_id] ] 182 else 183 ssh_options[:keys_only] = true 184 ssh_options[:key_data] = [ @@auth[auth_id].private_key ] 185 end 186 else 187 ssh_options[:password] = config[:password] if config[:password] 188 end 189 190 ssh_options[:port] = port 191 192 if reset || ! @@sessions.has_key?(session_id) 193 @@sessions[session_id] = Net::SSH.start(hostname, user, ssh_options.export) 194 end 195 end 196 yield(@@sessions[session_id]) if block_given? && @@sessions[session_id] 197 @@sessions[session_id] 198 end
session_id(hostname, user)
click to toggle source
# File lib/core/util/ssh.rb 151 def self.session_id(hostname, user) 152 "#{hostname}-#{user}" 153 end
terminal(hostname, user, options = {})
click to toggle source
Inspired by vagrant ssh implementation
See: github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/ssh.rb
# File lib/core/util/ssh.rb 373 def self.terminal(hostname, user, options = {}) 374 config = Config.ensure(options) 375 ssh_path = nucleon_locate("ssh") 376 377 raise Errors::SSHUnavailable unless ssh_path 378 379 port = config.get(:port, 22) 380 private_keys = config.get(:private_keys, File.join(ENV['HOME'], '.ssh', 'id_rsa')) 381 key_dir = config.get(:key_dir, nil) 382 key_name = config.get(:key_name, 'default') 383 384 command_options = [ 385 "#{user}@#{hostname}", 386 "-p", port.to_s, 387 "-o", "Compression=yes", 388 "-o", "DSAAuthentication=yes", 389 "-o", "LogLevel=FATAL", 390 "-o", "StrictHostKeyChecking=no", 391 "-o", "UserKnownHostsFile=/dev/null", 392 "-o", "IdentitiesOnly=yes" 393 ] 394 395 Util::Data.array(private_keys).each do |private_key| 396 unless ENV['NUCLEON_NO_SSH_KEY_SAVE'] || key_dir.nil? 397 keypair = unlock_private_key(private_key, { 398 :key_dir => key_dir, 399 :key_name => key_name 400 }) 401 private_key = keypair.private_key_file(key_dir, key_name) if keypair 402 end 403 command_options += [ "-i", File.expand_path(private_key) ] 404 end 405 406 if config.get(:forward_x11, false) 407 command_options += [ 408 "-o", "ForwardX11=yes", 409 "-o", "ForwardX11Trusted=yes" 410 ] 411 end 412 413 command_options += [ "-o", "ProxyCommand=#{config[:proxy_command]}" ] if config.get(:proxy_command, false) 414 command_options += [ "-o", "ForwardAgent=yes" ] if config.get(:forward_agent, false) 415 416 command_options.concat(Util::Data.array(config[:extra_args])) if config.get(:extra_args, false) 417 418 #--- 419 420 logger.info("Executing SSH in subprocess: #{command_options.inspect}") 421 422 process = ChildProcess.build('ssh', *command_options) 423 process.io.inherit! 424 425 process.start 426 process.wait 427 process.exit_code 428 end
unlock_private_key(private_key, options = {})
click to toggle source
# File lib/core/util/ssh.rb 433 def self.unlock_private_key(private_key, options = {}) 434 require 'net/ssh' 435 436 config = Config.ensure(options) 437 keypair = config.get(:keypair, nil) 438 key_dir = config.get(:key_dir, nil) 439 key_name = config.get(:key_name, 'default') 440 no_file = ENV['NUCLEON_NO_SSH_KEY_SAVE'] 441 442 password = nil 443 tmp_key_dir = nil 444 445 if private_key 446 keypair = nil if keypair && ! keypair.private_key 447 448 unless no_file 449 if key_dir && key_name && File.exists?(private_key) && loaded_private_key = Util::Disk.read(private_key) 450 FileUtils.mkdir_p(key_dir) 451 452 loaded_private_key =~ /BEGIN\s+([A-Z]+)\s+/ 453 454 local_key_type = $1 455 local_key_name = Keypair.render(local_key_type, key_name) 456 local_key_path = File.join(key_dir, local_key_name) 457 458 keypair = generate({ :private_key => local_key_path }) if File.exists?(local_key_path) 459 end 460 end 461 462 unless keypair 463 key_manager_logger = ::Logger.new(STDERR) 464 key_manager_logger.level = ::Logger::FATAL 465 key_manager = Net::SSH::Authentication::KeyManager.new(key_manager_logger) 466 467 key_manager.each_identity do |identity| 468 if identity.comment == private_key 469 # Feed the key to the system password manager if it exists 470 keypair = private_key 471 end 472 end 473 key_manager.finish 474 475 until keypair 476 keypair = generate({ 477 :private_key => private_key, 478 :passphrase => password 479 }) 480 password = ui.ask("Enter passphrase for #{private_key}: ", { :echo => false }) unless keypair 481 end 482 483 unless no_file 484 if key_dir && key_name && ! keypair.is_a?(String) 485 key_files = keypair.store(key_dir, key_name, false) 486 487 if key_files && File.exists?(key_files[:private_key]) 488 keypair = generate({ :private_key => key_files[:private_key] }) 489 end 490 end 491 end 492 end 493 end 494 keypair 495 end
upload(hostname, user, local_path, remote_path, options = {}) { |name, sent, total| ... }
click to toggle source
# File lib/core/util/ssh.rb 331 def self.upload(hostname, user, local_path, remote_path, options = {}) 332 config = Config.ensure(options) 333 334 require 'net/scp' 335 336 # Accepted options: 337 # * :recursive - the +local+ parameter refers to a local directory, which 338 # should be uploaded to a new directory named +remote+ on the remote 339 # server. 340 # * :preserve - the atime and mtime of the file should be preserved. 341 # * :verbose - the process should result in verbose output on the server 342 # end (useful for debugging). 343 # * :chunk_size - the size of each "chunk" that should be sent. Defaults 344 # to 2048. Changing this value may improve throughput at the expense 345 # of decreasing interactivity. 346 # 347 config.init(:recursive, true) 348 config.init(:preserve, true) 349 config.init(:verbose, true) 350 config.init(:chunk_size, 2048) 351 352 blocking = config.delete(:blocking, true) 353 354 session(hostname, user) do |ssh| 355 if blocking 356 ssh.scp.upload!(local_path, remote_path, config.export) do |ch, name, sent, total| 357 yield(name, sent, total) if block_given? 358 end 359 else 360 ssh.scp.upload(local_path, remote_path, config.export) 361 end 362 end 363 end
valid?(public_ssh_key)
click to toggle source
# File lib/core/util/ssh.rb 75 def self.valid?(public_ssh_key) 76 SSHKey.valid_ssh_public_key?(public_ssh_key) 77 end