How to use the ruby Net::SSH gem to automate a NetScreen SSG


I recently had to automate the configuration of a NetScreen SSG device and decided to use ruby along with the net ssh gem to accomplish this.

The trouble is that when I use the basic ssh connection / command syntax listed on the github page I get an empty string back as a result, no matter what command I execute.

There is a way to automate ssh commands via the the channel api, though!

Updated May 21st, 2016: The NetScreen device is a far more wiley device than I had originally anticipated, so my original solution ended up not working. I dug deep and found a better way to automate the netscreen device.

References

 

 

Typical use of Net::SSH

For most ordinary devices, this method of executing commands over ssh will work:

require 'net/ssh'
Net::SSH.start('host', 'user', :password => "password") do |ssh|

# capture all stderr and stdout output from a remote process
output = ssh.exec!("ping google.com")
puts output

Unfortunately, when paired with the antiquated NetScreen SSG device, this code produces an empty string response no matter what command I send!

 

NetScreen Usage

In order for me to accomplish my objectives (creating address objects and setting policies) I had to turn to Net::SSH::Telnet. It apparently knows how to execute the correct SSH commands to operate with old, crappy device-specific shells!:

require 'net/ssh'
require 'net/ssh/telnet'

class NetscreenSsh
  def initialize(host, user, key)
    @host = host
    @user = user
    @key = key
  end

  #
  # Creates a NetScreen address if it doesn't exit
  # Returns the address object name
  def get_set_address(zone, address)
    address_name = "suspicious_#{address}"
    get_existing_address_command = "get address #{zone} name suspicious_#{address}"
    set_address_command = "set address #{zone} suspicious_#{address} #{address} 255.255.255.255"

    #
    # If we've already got the address, exit!
    address_result = execute_command(get_existing_address_command)
    #puts "get address result: #{address_result}"
    if !(address_result.to_s.include?('address not found'))
      #puts "returning addressname!"
      return address_name
    end

    #
    # Create the address if we don't already have it
    set_result = execute_command(set_address_command)
    #puts "set address result: #{set_result}"
    if(set_result.include?("Illegal"))
      puts "Invalid command syntax, exiting!"
      exit(400)
    else
      #puts "had to set the address name, returning it!"
      return address_name
    end

  end

  #
  # Sets policy on the netscreen device we are connected to
  # Policy is placed at the "TOP" of the list!
  # Returns the id of the policy that is created
  def set_policy(policy_command)
    policy_output = execute_command(policy_command)
    policy_id = policy_output.match(/policy id = ([0-9]+)/)[1]
    return policy_id
  end

  #
  # Meat and potatoes: This actually performs the requested operations
  #    If you don't want to use key-based authentication you can use a password instead
  def execute_command(command, write_output_to_console = true)
    @output = ""
    #
    # SSHy stuff
    # Debugging option, append as criteria to the 'start' method
    #   , :verbose => :debug
    Net::SSH.start(@host, @user, :keys => [@key]) do |ssh|
      shell = Net::SSH::Telnet.new("Session" => ssh)

      @output = shell.cmd(command)
    end

    return @output
  end
end

 

 

[Broken] Channel API solution

Update May 2016: Yeah, this code is pretty much worthless. It will only work if you already have an ssh session established from your workstation. So, if you were to try and use the below channel API code to run an automated behind-the-scenes process you would fail miserably.

The other problem with the channel API as I had originally implemented is that the commands are executed in parallel rather than in serial, which causes unexpected results even if you are SSH'd into the netscreen via your workstation!

Fortunately, the net ssh gem includes a channel api that gives us more fine-grained control over our ssh connections.

This seems to work for me:

require 'net/ssh' host = '192.168.2.22' user = 'dsc' key = "/path/to/ssh_key" Net::SSH.start(host, user, :keys => [key]) do |ssh| 
ssh.open_channel do |channel| channel.send_channel_request "shell" channel.on_data do |ch, data| 
puts "got data #{data.inspect}" end channel.on_extended_data do |ch, type, data| puts "got stderr: #{data}" end 
channel.on_close do |ch| puts "channel is closing!" end channel.send_data("ping google.com\r\n") channel.send_data("exit\r\n") end
 ssh.loop end

 

The \r\n at the end of each send_data command is required for ScreenOS to execute the commands.