Class: ASAConsole

Inherits:
Object
  • Object
show all
Defined in:
lib/asa_console.rb,
lib/asa_console/test.rb,
lib/asa_console/util.rb,
lib/asa_console/error.rb,
lib/asa_console/config.rb,
lib/asa_console/terminal.rb,
lib/asa_console/test/script.rb,
lib/asa_console/terminal/ssh.rb,
lib/asa_console/terminal/fake_ssh.rb

Overview

Command line interface to a Cisco ASA.

Examples:

# Create an instance of ASAConsole for an SSH connection
asa = ASAConsole.ssh(
  host: 'fw01.example.com', # Required by Net::SSH
  user: 'admin',            # Required by Net::SSH
  password: 'mypass',       # Optional in case you want to use SSH keys
  connect_timeout: 3,       # Converted to the Net::SSH :timeout value
  command_timeout: 10,      # How long to wait for a command to execute?
  enable_password: 'secret' # Optional if it's the same as :password
)

# Other Net::SSH options can be set before connecting
asa.terminal.ssh_opts[:port] = 2022

asa.connect
puts asa.show('version')
asa.disconnect

Defined Under Namespace

Modules: Error, Terminal, Test, Util Classes: Config, Exception

Constant Summary collapse

PASSWORD_PROMPT =
/^Password: +\z/
EXEC_PROMPT =
/^[\w\.\/-]+> +\z/
PRIV_EXEC_PROMPT =
/^[\w\.\/-]+(?:\(config[\s\w-]*\))?# +\z/
ANY_EXEC_PROMPT =
/^[\w\.\/-]+(?:(?:\(config[\s\w-]*\))?#|>) +\z/
CONFIG_PROMPT =
/^[\w\.\/-]+\(config[\s\w-]*\)# +\z/
INVALID_CMD_CHAR =
/[^\x20-\x3e\x40-\x7e]/
CONFIG_MODE_REGEX =
/^[\w\.\-\/]+\((config[\s\w\-]*)\)# $/
CMD_ERROR_REGEX =
/^ERROR: (?:% )?(.*)/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#config_modeString? (readonly)

Configuration submode string (e.g. "config", "config-if") or nil if not in a configuration mode

Returns:

  • (String, nil)

    the current value of config_mode


36
37
38
# File 'lib/asa_console.rb', line 36

def config_mode
  @config_mode
end

#enable_passwordString?

Enable password or nil if the enble password is not required

Returns:

  • (String, nil)

    the current value of enable_password


36
37
38
# File 'lib/asa_console.rb', line 36

def enable_password
  @enable_password
end

#terminalObject

An instance of a class from the Terminal module

Returns:

  • (Object)

    the current value of terminal


36
37
38
# File 'lib/asa_console.rb', line 36

def terminal
  @terminal
end

Class Method Details

.ssh(opts) ⇒ ASAConsole

Factory method for an SSH console session.

Parameters:

  • opts (Hash)

    a customizable set of options

Options Hash (opts):

Returns:

See Also:


70
71
72
73
74
# File 'lib/asa_console.rb', line 70

def ssh(opts)
  enable_password = opts.delete(:enable_password)
  terminal = Terminal::SSH.new(opts)
  new terminal, enable_password
end

Instance Method Details

#config_exec(command, opts = {}) ⇒ String

Execute a command in any configuration mode.

Parameters:

  • command (String)
  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :expect_prompt (Regexp)

    prompt expected after executing the command

  • :ignore_output (Boolean)

    if true, do not raise an error when the command generates output (unless the output is an error message)

  • :ignore_errors (Boolean)

    if true, ignore error messages in the output (implies :ignore_output)

  • :require_config_mode (Boolean)

    a specific configuration submode required to execute the command

Returns:

  • (String)

    output from the command, if any


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
# File 'lib/asa_console.rb', line 144

def config_exec(command, opts = {})
  must_be_connected!
  enable! if @terminal.prompt =~ EXEC_PROMPT

  expect_prompt = opts.fetch(:expect_prompt, CONFIG_PROMPT)
  ignore_output = opts.fetch(:ignore_output, false)
  ignore_errors = opts.fetch(:ignore_errors, false)
  require_config_mode = opts[:require_config_mode]

  unless @terminal.prompt =~ CONFIG_PROMPT
    send('configure terminal', CONFIG_PROMPT)
  end

  if require_config_mode && @config_mode != require_config_mode
    message = "Will not execute command in '%s' mode (expected '%s')"
    fail Error::ConfigModeError, message % [ @config_mode, require_config_mode ]
  end

  # Any part of the config may change, so clear the cache.
  @running_config = {}

  output = send(command, expect_prompt)

  unless ignore_errors
    error = CMD_ERROR_REGEX.match(output)
    fail Error::CommandError, "Error output after executing '#{command}': #{error[1]}" if error
    unless ignore_output || output.empty?
      fail Error::UnexpectedOutputError, "Unexpected output after executing '#{command}': #{output}"
    end
  end

  output
end

#config_exec!(command, opts = {}) ⇒ String

Execute a command from top-level configuration mode. This method is a wrapper for #config_exec.

Parameters:

  • command (String)
  • opts (Hash) (defaults to: {})

    options for #config_exec

Returns:

  • (String)

    output from the command, if any

See Also:


185
186
187
188
189
# File 'lib/asa_console.rb', line 185

def config_exec!(command, opts = {})
  must_be_connected!
  send('exit', CONFIG_PROMPT) while @config_mode && @config_mode != 'config'
  config_exec(command, opts)
end

#connectvoid

This method returns an undefined value.


93
94
95
96
# File 'lib/asa_console.rb', line 93

def connect
  @terminal.connect
  priv_exec! 'terminal pager lines 0'
end

#connected?Boolean

Returns:

  • (Boolean)

99
100
101
# File 'lib/asa_console.rb', line 99

def connected?
  @terminal.connected?
end

#disconnectvoid

This method returns an undefined value.


104
105
106
107
108
109
# File 'lib/asa_console.rb', line 104

def disconnect
  while @terminal.connected? && @terminal.prompt =~ ANY_EXEC_PROMPT
    @terminal.send('exit', ANY_EXEC_PROMPT) { |success| break unless success }
  end
  @terminal.disconnect
end

#priv_exec(command) ⇒ String

Execute a command in any privileged EXEC mode (includes config EXEC modes).

Parameters:

  • command (String)

Returns:

  • (String)

    output from the command, if any


195
196
197
198
199
200
201
202
# File 'lib/asa_console.rb', line 195

def priv_exec(command)
  must_be_connected!
  enable! if @terminal.prompt =~ EXEC_PROMPT
  output = send(command, PRIV_EXEC_PROMPT)
  error = CMD_ERROR_REGEX.match(output)
  fail Error::CommandError, "Error output after executing '#{command}': #{error[1]}" if error
  output
end

#priv_exec!(command) ⇒ String

Execute a command in privileged EXEC mode (excludes config EXEC modes). This method is a wrapper for #priv_exec.

Parameters:

  • command (String)

Returns:

  • (String)

    output from the command, if any

See Also:


210
211
212
213
214
# File 'lib/asa_console.rb', line 210

def priv_exec!(command)
  must_be_connected!
  send('exit', PRIV_EXEC_PROMPT) while @config_mode
  priv_exec(command)
end

#running_config(subcmd = nil) ⇒ Config

Execute a "show running-conifg [...]" command and load the results into a Config object.

Parameters:

  • subcmd (String, nil) (defaults to: nil)

Returns:

See Also:


230
231
232
233
234
235
236
237
238
# File 'lib/asa_console.rb', line 230

def running_config(subcmd = nil)
  unless @running_config.key? subcmd
    output = subcmd ? show('running-config ' + subcmd) : show('running-config')
    @running_config[subcmd] = Config.new(nested_config: output)
  end
  @running_config[subcmd]
rescue Error::CommandError
  Config.new
end

#send(line, expect_regex, is_password = false) ⇒ String

Send a line of text to the console and block until the expected prompt is seen in the output or a timeout is reached. Raises an exception if the expected prompt has not been received after waiting for command_timeout seconds following the last data transfer.

Parameters:

  • line (String)
  • expect_regex (Regexp)
  • is_password (Boolean) (defaults to: false)

    if true, line will be masked with asterisks in the session log

Returns:

  • (String)

    output from the command, if any

Raises:


122
123
124
125
126
127
128
129
# File 'lib/asa_console.rb', line 122

def send(line, expect_regex, is_password = false)
  must_be_connected!
  line = line.gsub(INVALID_CMD_CHAR, "\x16\\0") unless is_password
  @terminal.send(line, expect_regex, is_password) do |success, output|
    fail Error::ExpectedPromptFailure, "Expected prompt not found in output: #{output}" unless success
    output
  end
end

#show(subcmd) ⇒ String

A shortcut for running "show" commands.

Parameters:

  • subcmd (String)

Returns:

  • (String)

220
221
222
# File 'lib/asa_console.rb', line 220

def show(subcmd)
  priv_exec('show ' + subcmd)
end

#versionString

ASA software version in x.x(x) format.

Returns:

  • (String)

243
244
245
246
247
248
249
250
251
252
253
# File 'lib/asa_console.rb', line 243

def version
  unless @version
    # Reassemble the version string on the off chance that an interim release
    # is reported in the format "x.x(x.x)" versus "x.x(x)x".
    regex = /^Cisco Adaptive Security Appliance Software Version (\d+)\.(\d+)\((\d+).*?\)/
    matches = regex.match(show('version'))
    fail Error::VersionParseError, 'Unable to determine appliance version' unless matches
    @version = '%d.%d(%d)' % matches[1..3]
  end
  @version
end

#version?(*exprs) ⇒ Boolean

Return the result of comparing the ASA software version with a list of expressions. Will yield once on success if a block is given.

Examples:

asa.version? '9.x', '< 9.3' do
  puts 'Running version 9.0, 9.1 or 9.2'
end

Parameters:

  • exprs (Array<String>)

Returns:

  • (Boolean)

    true if all expressions match, or false otherwise

See Also:


266
267
268
269
270
# File 'lib/asa_console.rb', line 266

def version?(*exprs)
  success = Util.version_match?(version, exprs)
  yield if success && block_given?
  success
end