asa_console 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.yardopts +8 -0
- data/LICENSE.md +22 -0
- data/README.md +116 -0
- data/asa_console.gemspec +30 -0
- data/bin/asatest +113 -0
- data/lib/asa_console.rb +283 -0
- data/lib/asa_console/config.rb +170 -0
- data/lib/asa_console/error.rb +43 -0
- data/lib/asa_console/terminal.rb +9 -0
- data/lib/asa_console/terminal/fake_ssh.rb +145 -0
- data/lib/asa_console/terminal/ssh.rb +223 -0
- data/lib/asa_console/test.rb +134 -0
- data/lib/asa_console/test/script.rb +48 -0
- data/lib/asa_console/util.rb +151 -0
- data/script/test_clock.rb +39 -0
- data/script/test_connect.rb +10 -0
- data/script/test_error_command.rb +15 -0
- data/script/test_error_connect.rb +31 -0
- data/script/test_error_enable.rb +18 -0
- data/script/test_names.rb +33 -0
- data/script/test_object.rb +50 -0
- data/script/test_terminal.rb +25 -0
- data/script/test_version.rb +31 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Yzc1YjUzZDY2YWUwMjY2Y2IyZTM2YTcyMDQ2M2M2MjE4MDVmYmNkYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZGFkZjI4YjZlNTc3OTExYmY0YTA2MGFmYjc2YWVlZDk3Nzk3NzFlMw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MTRhYjE5YzE5ZDhiYjQzZWJmNGM2NTM0ZDA3YTdjNzczM2VkZjYyYTc4ZDJm
|
10
|
+
ZTFiN2I2MjAzZTUxNTYzNDUxYzU1N2I5MGJlNTZhNThjNWJkYmRlNzk4NzQz
|
11
|
+
M2I1YWExNGExZWE3YzZhZjM2YTQ2MmQyNWYzZDQ2NjQyMTU0OWU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MTE2YTQ4NTJjZGUzMTU2MzkyZWY4YTVhMGVlOGM3YzhhMTQzMDUzOGViZTIz
|
14
|
+
YzY0MzMzNzFhMjJjOTY2M2JkMGIzYmZiOWRhZGZlYWRjYTI4NDlhYjQ5NmQy
|
15
|
+
MWM1MjFmNDkwYmUwMjMzNGQ0ODZkMmY4ZDU1M2Y4ZTgzODVmN2Y=
|
data/.yardopts
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright (c) 2015 Henry Goodman
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/hgoodman/asa-console.svg?branch=master)](https://travis-ci.org/hgoodman/asa-console)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/hgoodman/asa-console/badges/gpa.svg)](https://codeclimate.com/github/hgoodman/asa-console)
|
3
|
+
[![Test Coverage](https://codeclimate.com/github/hgoodman/asa-console/badges/coverage.svg)](https://codeclimate.com/github/hgoodman/asa-console/coverage)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/asa_console.svg)](https://badge.fury.io/rb/asa_console)
|
5
|
+
[![YARD Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/hgoodman/asa-console/)
|
6
|
+
|
7
|
+
ASAConsole
|
8
|
+
==========
|
9
|
+
|
10
|
+
A ruby gem for Cisco ASA management via an interactive terminal session.
|
11
|
+
|
12
|
+
This gem lets a program interact with a Cisco ASA using CLI commands. It includes a minimal set of functions for issuing commands and parsing the results.
|
13
|
+
|
14
|
+
Caveats
|
15
|
+
=======
|
16
|
+
|
17
|
+
Most people would be better off using [Cisco's official REST API](http://www.cisco.com/c/en/us/td/docs/security/asa/api/qsg-asa-api.html) plugin for the ASA platform. This gem does not use the supported API. It was developed as an academic pursuit and may not be suitable for your environment. It is distributed under the [MIT License](LICENSE.md), which is to say that it comes with no warranty of any kind.
|
18
|
+
|
19
|
+
That being said, you might find it useful if you are working with older hardware or if you have other special requirements. The official REST API plugin is only supported on 5500-X, ASAv and newer platforms.
|
20
|
+
|
21
|
+
For the time being, direct SSH is the only transport method implemented by this gem although it could easily be extended to support alternatives like using a serial console or a jump box.
|
22
|
+
|
23
|
+
Getting Started
|
24
|
+
===============
|
25
|
+
|
26
|
+
The easiest way to get started is to browse through the test files in the [script](script/) folder. To supplement its automated tests, this gem provides a framework for live testing against devices in a lab environment. There are several canned test scripts that demonstrate different features of the library.
|
27
|
+
|
28
|
+
Each script executes a series of commands declared in a block as show below. The test runner displays output as it would appear in an SSH session and adds color to indicate how the output is being parsed. Informational messages can be added to the output with the `log` method.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
ASAConsole::Test.script do |asa|
|
32
|
+
log 'Connecting...'
|
33
|
+
asa.connect
|
34
|
+
if asa.version? '>= 9.4(1)'
|
35
|
+
asa.priv_exec 'no terminal interactive'
|
36
|
+
else
|
37
|
+
log 'The "no terminal interactive" command is not supported'
|
38
|
+
end
|
39
|
+
log 'Disconnecting...'
|
40
|
+
asa.disconnect
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
The included test scripts are designed to be non-invasive and to leave the device configuration in its original state. Nevertheless, running them in a production environment is not recommended.
|
45
|
+
|
46
|
+
Command Line Utility
|
47
|
+
--------------------
|
48
|
+
|
49
|
+
**Usage**
|
50
|
+
|
51
|
+
asatest [options] <testname> [asaname]
|
52
|
+
|
53
|
+
**Examples**
|
54
|
+
|
55
|
+
List command line options and available tests:
|
56
|
+
|
57
|
+
asatest --help
|
58
|
+
|
59
|
+
Execute a canned test:
|
60
|
+
|
61
|
+
asatest connect
|
62
|
+
|
63
|
+
Load a custom test file:
|
64
|
+
|
65
|
+
asatest -f ./my_test_file.rb
|
66
|
+
|
67
|
+
Configuration Files
|
68
|
+
-------------------
|
69
|
+
|
70
|
+
The `asatest` executable will read a list of default command line options from the file `~/.asa-console/test_options.yaml`. Here is an example of the file format:
|
71
|
+
|
72
|
+
```yaml
|
73
|
+
---
|
74
|
+
show-session-log: true
|
75
|
+
color: light
|
76
|
+
```
|
77
|
+
|
78
|
+
Each key matches a long-form command line option with the leading "--" removed. Run the program with "--help" for a complete list.
|
79
|
+
|
80
|
+
Device information is needed for running live tests. By default, the program will look for appliance information in `~/.asa-console/test_appliances.yaml`. Here is an example of the file format:
|
81
|
+
|
82
|
+
```yaml
|
83
|
+
---
|
84
|
+
default_appliance: firewall002
|
85
|
+
appliances:
|
86
|
+
firewall001:
|
87
|
+
terminal_opts:
|
88
|
+
host: 10.7.7.1
|
89
|
+
connect_timeout: 20
|
90
|
+
firewall002:
|
91
|
+
terminal_opts:
|
92
|
+
host: 10.7.7.254
|
93
|
+
user: testuser
|
94
|
+
password: execpass
|
95
|
+
enable_password: enablepass
|
96
|
+
```
|
97
|
+
|
98
|
+
If any of the following options are not found in this file, the user will be prompted to enter values for them.
|
99
|
+
|
100
|
+
- `terminal_opts[host]`
|
101
|
+
- `terminal_opts[user]`
|
102
|
+
- `terminal_opts[password]`
|
103
|
+
- `enable_password`
|
104
|
+
|
105
|
+
The enable password can be omitted if it is the same as the terminal password or if it is otherwise not needed.
|
106
|
+
|
107
|
+
API Documentation
|
108
|
+
=================
|
109
|
+
|
110
|
+
You can view online documentation at [rubydoc.info](http://www.rubydoc.info/github/hgoodman/asa-console/) or generate it yourself with:
|
111
|
+
|
112
|
+
rake yardoc
|
113
|
+
|
114
|
+
To include documentation for objects used in testing and development:
|
115
|
+
|
116
|
+
rake yardoc_dev
|
data/asa_console.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'asa_console'
|
4
|
+
s.version = '0.1.2'
|
5
|
+
s.summary = 'Cisco ASA management via an interactive terminal session'
|
6
|
+
s.description = 'This gem lets a program interact with a Cisco ASA using CLI
|
7
|
+
commands. It includes a minimal set of functions for issuing commands and
|
8
|
+
parsing the results.'.gsub(/\s+/, ' ')
|
9
|
+
s.author = 'Henry Goodman'
|
10
|
+
s.email = 'github@henrygoodman.com'
|
11
|
+
s.homepage = 'https://github.com/hgoodman/asa-console/'
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.has_rdoc = 'yard'
|
14
|
+
s.files = Dir[__FILE__, 'lib/**/*', 'script/*', '*.md', '.yardopts']
|
15
|
+
s.executables = ['asatest']
|
16
|
+
|
17
|
+
s.add_runtime_dependency('net-ssh', '~> 2.9.2')
|
18
|
+
s.add_runtime_dependency('highline', '~> 1.7')
|
19
|
+
|
20
|
+
s.add_development_dependency('bundler', '~> 1.7')
|
21
|
+
s.add_development_dependency('rake', '~> 10.4')
|
22
|
+
s.add_development_dependency('redcarpet', '~> 3.3')
|
23
|
+
s.add_development_dependency('rspec', '~> 3.4')
|
24
|
+
s.add_development_dependency('rubocop', '~> 0.35')
|
25
|
+
s.add_development_dependency('simplecov', '~> 0.11')
|
26
|
+
s.add_development_dependency('thor', '~> 0.19')
|
27
|
+
s.add_development_dependency('yard', '~> 0.8')
|
28
|
+
|
29
|
+
s.required_ruby_version = '>=1.9.3'
|
30
|
+
end
|
data/bin/asatest
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'getoptlong'
|
5
|
+
require 'highline/import'
|
6
|
+
require 'asa_console/test'
|
7
|
+
|
8
|
+
DEFAULT_OPTION_FILE = File.join(Dir.home, '.asa-console', 'test_options.yaml')
|
9
|
+
DEFAULT_APPLIANCE_FILE = File.join(Dir.home, '.asa-console', 'test_appliances.yaml')
|
10
|
+
DEFAULT_COLOR = :dark
|
11
|
+
|
12
|
+
def usage
|
13
|
+
puts %(
|
14
|
+
Usage:
|
15
|
+
#{$PROGRAM_NAME} [options] <testname> [asaname]
|
16
|
+
|
17
|
+
Options:
|
18
|
+
-h, --help Display this message
|
19
|
+
-f, --file Interpret <testname> as an explicit file name
|
20
|
+
-o, --option-file <file> YAML file with command line defaults for this utility
|
21
|
+
-a, --appliance-file <file> YAML file with ASA device list and terminal options
|
22
|
+
-s, --show-session-log Include the full session log in the command output
|
23
|
+
-c, --color (light|dark|off) Colorize output using light or dark colors
|
24
|
+
|
25
|
+
Defaults:
|
26
|
+
--option-file #{DEFAULT_OPTION_FILE}
|
27
|
+
--appliance-file #{DEFAULT_APPLIANCE_FILE}
|
28
|
+
--color #{DEFAULT_COLOR}
|
29
|
+
|
30
|
+
Available Tests:
|
31
|
+
#{ASAConsole::Test.test_names.join("\n ")}
|
32
|
+
).gsub(/^ /, '')
|
33
|
+
puts
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
cli_opts = {}
|
38
|
+
|
39
|
+
begin
|
40
|
+
cli = GetoptLong.new(
|
41
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
42
|
+
[ '--file', '-f', GetoptLong::NO_ARGUMENT ],
|
43
|
+
[ '--option-file', '-o', GetoptLong::REQUIRED_ARGUMENT ],
|
44
|
+
[ '--appliance-file', '-a', GetoptLong::REQUIRED_ARGUMENT ],
|
45
|
+
[ '--show-session-log', '-s', GetoptLong::NO_ARGUMENT ],
|
46
|
+
[ '--color', '-c', GetoptLong::REQUIRED_ARGUMENT ]
|
47
|
+
)
|
48
|
+
cli.each do |opt, arg|
|
49
|
+
cli_opts[opt.sub(/^--/, '')] = (arg == '' ? true : arg)
|
50
|
+
end
|
51
|
+
rescue GetoptLong::MissingArgument
|
52
|
+
usage
|
53
|
+
end
|
54
|
+
|
55
|
+
help = false
|
56
|
+
is_file = false
|
57
|
+
option_file = DEFAULT_OPTION_FILE
|
58
|
+
appliance_file = DEFAULT_APPLIANCE_FILE
|
59
|
+
show_session_log = false
|
60
|
+
color = DEFAULT_COLOR
|
61
|
+
|
62
|
+
option_file = cli_opts['option-file'] if cli_opts['option-file']
|
63
|
+
if File.readable? option_file
|
64
|
+
cli_opts = YAML.load_file(option_file).merge(cli_opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
cli_opts.each do |opt, arg|
|
68
|
+
case opt
|
69
|
+
when 'help' then help = arg
|
70
|
+
when 'file' then is_file = arg
|
71
|
+
when 'appliance-file' then appliance_file = arg
|
72
|
+
when 'show-session-log' then show_session_log = arg
|
73
|
+
when 'color' then color = arg.to_sym
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
test_name = ARGV.shift if ARGV.length > 0
|
78
|
+
asa_name = ARGV.shift if ARGV.length > 0
|
79
|
+
|
80
|
+
ASAConsole::Test.color_scheme = color
|
81
|
+
|
82
|
+
enable_password = nil
|
83
|
+
|
84
|
+
if File.readable? appliance_file
|
85
|
+
config = YAML.load_file(appliance_file)
|
86
|
+
asa_name ||= config['default_appliance']
|
87
|
+
if asa_name
|
88
|
+
if config['appliances'][asa_name].is_a?(Hash)
|
89
|
+
terminal_opts = config['appliances'][asa_name]['terminal_opts']
|
90
|
+
enable_password = config['appliances'][asa_name]['enable_password']
|
91
|
+
else
|
92
|
+
puts "Error: Configuration missing for #{asa_name.dump}"
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
usage if help || test_name.nil?
|
99
|
+
|
100
|
+
test_file = is_file ? File.expand_path(test_name) : ASAConsole::Test.test_path(test_name)
|
101
|
+
|
102
|
+
terminal_opts ||= {}
|
103
|
+
terminal_opts.keys.each { |key| terminal_opts[key.to_sym] = terminal_opts.delete(key) }
|
104
|
+
terminal_opts[:host] = ask 'Host Name: ' unless terminal_opts[:host]
|
105
|
+
terminal_opts[:user] = ask 'User Name: ' unless terminal_opts[:user]
|
106
|
+
terminal_opts[:password] = ask('Password: ') { |q| q.echo = '*' } unless terminal_opts[:password]
|
107
|
+
|
108
|
+
unless enable_password
|
109
|
+
pw = ask('Enable Password [ENTER for none]: ') { |q| q.echo = '*' }
|
110
|
+
enable_password = pw if pw.length > 0
|
111
|
+
end
|
112
|
+
|
113
|
+
exit 1 unless ASAConsole::Test.start(test_file, terminal_opts, enable_password, show_session_log)
|
data/lib/asa_console.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
|
2
|
+
require 'asa_console/config'
|
3
|
+
require 'asa_console/error'
|
4
|
+
require 'asa_console/terminal/fake_ssh'
|
5
|
+
require 'asa_console/terminal/ssh'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Command line interface to a Cisco ASA.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # Create an instance of ASAConsole for an SSH connection
|
12
|
+
# asa = ASAConsole.ssh(
|
13
|
+
# host: 'fw01.example.com', # Required by Net::SSH
|
14
|
+
# user: 'admin', # Required by Net::SSH
|
15
|
+
# password: 'mypass', # Optional in case you want to use SSH keys
|
16
|
+
# connect_timeout: 3, # Converted to the Net::SSH :timeout value
|
17
|
+
# command_timeout: 10, # How long to wait for a command to execute?
|
18
|
+
# enable_password: 'secret' # Optional if it's the same as :password
|
19
|
+
# )
|
20
|
+
#
|
21
|
+
# # Other Net::SSH options can be set before connecting
|
22
|
+
# asa.terminal.ssh_opts[:port] = 2022
|
23
|
+
#
|
24
|
+
# asa.connect
|
25
|
+
# puts asa.show('version')
|
26
|
+
# asa.disconnect
|
27
|
+
#
|
28
|
+
# @attr_reader config_mode [String, nil]
|
29
|
+
# Configuration submode string (e.g. "config", "config-if") or `nil` if not
|
30
|
+
# in a configuration mode
|
31
|
+
# @attr terminal [Object]
|
32
|
+
# An instance of a class from the {Terminal} module
|
33
|
+
# @attr enable_password [String, nil]
|
34
|
+
# Enable password or `nil` if the enble password is not required
|
35
|
+
#
|
36
|
+
class ASAConsole
|
37
|
+
attr_reader :config_mode
|
38
|
+
attr_accessor :terminal
|
39
|
+
attr_accessor :enable_password
|
40
|
+
|
41
|
+
PASSWORD_PROMPT = /^Password: +\z/
|
42
|
+
EXEC_PROMPT = /^[\w\.\/-]+> +\z/
|
43
|
+
PRIV_EXEC_PROMPT = /^[\w\.\/-]+(?:\(config[\s\w-]*\))?# +\z/
|
44
|
+
ANY_EXEC_PROMPT = /^[\w\.\/-]+(?:(?:\(config[\s\w-]*\))?#|>) +\z/
|
45
|
+
CONFIG_PROMPT = /^[\w\.\/-]+\(config[\s\w-]*\)# +\z/
|
46
|
+
INVALID_CMD_CHAR = /[^\x20-\x3e\x40-\x7e]/
|
47
|
+
CONFIG_MODE_REGEX = /^[\w\.\-\/]+\((config[\s\w\-]*)\)# $/
|
48
|
+
CMD_ERROR_REGEX = /^ERROR: (?:% )?(.*)/
|
49
|
+
|
50
|
+
class << self
|
51
|
+
# Factory method for a fake SSH console session.
|
52
|
+
#
|
53
|
+
# @api development
|
54
|
+
# @see Terminal::FakeSSH#initialize Terminal::FakeSSH constructor
|
55
|
+
# @option opts ... options for the {Terminal::FakeSSH} constructor
|
56
|
+
# @option opts [String] :enable_password
|
57
|
+
# @return [ASAConsole]
|
58
|
+
def fake_ssh(opts)
|
59
|
+
enable_password = opts.delete(:enable_password)
|
60
|
+
terminal = Terminal::FakeSSH.new(opts)
|
61
|
+
new terminal, enable_password
|
62
|
+
end
|
63
|
+
|
64
|
+
# Factory method for an SSH console session.
|
65
|
+
#
|
66
|
+
# @see Terminal::SSH#initialize Terminal::SSH constructor
|
67
|
+
# @option opts ... options for the {Terminal::SSH} constructor
|
68
|
+
# @option opts [String] :enable_password
|
69
|
+
# @return [ASAConsole]
|
70
|
+
def ssh(opts)
|
71
|
+
enable_password = opts.delete(:enable_password)
|
72
|
+
terminal = Terminal::SSH.new(opts)
|
73
|
+
new terminal, enable_password
|
74
|
+
end
|
75
|
+
|
76
|
+
private :new
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def initialize(terminal, enable_password = nil)
|
81
|
+
@config_mode = nil
|
82
|
+
@terminal = terminal
|
83
|
+
@terminal.on_output do
|
84
|
+
matches = CONFIG_MODE_REGEX.match(@terminal.prompt)
|
85
|
+
@config_mode = matches ? matches[1] : nil
|
86
|
+
end
|
87
|
+
@enable_password = enable_password
|
88
|
+
@version = nil
|
89
|
+
@running_config = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [void]
|
93
|
+
def connect
|
94
|
+
@terminal.connect
|
95
|
+
priv_exec! 'terminal pager lines 0'
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Boolean]
|
99
|
+
def connected?
|
100
|
+
@terminal.connected?
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [void]
|
104
|
+
def disconnect
|
105
|
+
while @terminal.connected? && @terminal.prompt =~ ANY_EXEC_PROMPT
|
106
|
+
@terminal.send('exit', ANY_EXEC_PROMPT) { |success| break unless success }
|
107
|
+
end
|
108
|
+
@terminal.disconnect
|
109
|
+
end
|
110
|
+
|
111
|
+
# Send a line of text to the console and block until the expected prompt is
|
112
|
+
# seen in the output or a timeout is reached. Raises an exception if the
|
113
|
+
# expected prompt has not been received after waiting for `command_timeout`
|
114
|
+
# seconds following the last data transfer.
|
115
|
+
#
|
116
|
+
# @param line [String]
|
117
|
+
# @param expect_regex [Regexp]
|
118
|
+
# @param is_password [Boolean]
|
119
|
+
# if `true`, `line` will be masked with asterisks in the session log
|
120
|
+
# @raise [Error::ExpectedPromptFailure]
|
121
|
+
# @return [String] output from the command, if any
|
122
|
+
def send(line, expect_regex, is_password = false)
|
123
|
+
must_be_connected!
|
124
|
+
line = line.gsub(INVALID_CMD_CHAR, "\x16\\0") unless is_password
|
125
|
+
@terminal.send(line, expect_regex, is_password) do |success, output|
|
126
|
+
fail Error::ExpectedPromptFailure, "Expected prompt not found in output: #{output}" unless success
|
127
|
+
output
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Execute a command in any configuration mode.
|
132
|
+
#
|
133
|
+
# @param command [String]
|
134
|
+
# @option opts [Regexp] :expect_prompt
|
135
|
+
# prompt expected after executing the command
|
136
|
+
# @option opts [Boolean] :ignore_output
|
137
|
+
# if `true`, do not raise an error when the command generates output (unless
|
138
|
+
# the output is an error message)
|
139
|
+
# @option opts [Boolean] :ignore_errors
|
140
|
+
# if `true`, ignore error messages in the output (implies `:ignore_output`)
|
141
|
+
# @option opts [Boolean] :require_config_mode
|
142
|
+
# a specific configuration submode required to execute the command
|
143
|
+
# @return [String] output from the command, if any
|
144
|
+
def config_exec(command, opts = {})
|
145
|
+
must_be_connected!
|
146
|
+
enable! if @terminal.prompt =~ EXEC_PROMPT
|
147
|
+
|
148
|
+
expect_prompt = opts.fetch(:expect_prompt, CONFIG_PROMPT)
|
149
|
+
ignore_output = opts.fetch(:ignore_output, false)
|
150
|
+
ignore_errors = opts.fetch(:ignore_errors, false)
|
151
|
+
require_config_mode = opts[:require_config_mode]
|
152
|
+
|
153
|
+
unless @terminal.prompt =~ CONFIG_PROMPT
|
154
|
+
send('configure terminal', CONFIG_PROMPT)
|
155
|
+
end
|
156
|
+
|
157
|
+
if require_config_mode && @config_mode != require_config_mode
|
158
|
+
message = "Will not execute command in '%s' mode (expected '%s')"
|
159
|
+
fail Error::ConfigModeError, message % [ @config_mode, require_config_mode ]
|
160
|
+
end
|
161
|
+
|
162
|
+
# Any part of the config may change, so clear the cache.
|
163
|
+
@running_config = {}
|
164
|
+
|
165
|
+
output = send(command, expect_prompt)
|
166
|
+
|
167
|
+
unless ignore_errors
|
168
|
+
error = CMD_ERROR_REGEX.match(output)
|
169
|
+
fail Error::CommandError, "Error output after executing '#{command}': #{error[1]}" if error
|
170
|
+
unless ignore_output || output.empty?
|
171
|
+
fail Error::UnexpectedOutputError, "Unexpected output after executing '#{command}': #{output}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
output
|
176
|
+
end
|
177
|
+
|
178
|
+
# Execute a command from top-level configuration mode. This method is a
|
179
|
+
# wrapper for {#config_exec}.
|
180
|
+
#
|
181
|
+
# @see #config_exec
|
182
|
+
# @param command [String]
|
183
|
+
# @param opts [Hash] options for {#config_exec}
|
184
|
+
# @return [String] output from the command, if any
|
185
|
+
def config_exec!(command, opts = {})
|
186
|
+
must_be_connected!
|
187
|
+
send('exit', CONFIG_PROMPT) while @config_mode && @config_mode != 'config'
|
188
|
+
config_exec(command, opts)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Execute a command in any privileged EXEC mode (includes config EXEC modes).
|
192
|
+
#
|
193
|
+
# @param command [String]
|
194
|
+
# @return [String] output from the command, if any
|
195
|
+
def priv_exec(command)
|
196
|
+
must_be_connected!
|
197
|
+
enable! if @terminal.prompt =~ EXEC_PROMPT
|
198
|
+
output = send(command, PRIV_EXEC_PROMPT)
|
199
|
+
error = CMD_ERROR_REGEX.match(output)
|
200
|
+
fail Error::CommandError, "Error output after executing '#{command}': #{error[1]}" if error
|
201
|
+
output
|
202
|
+
end
|
203
|
+
|
204
|
+
# Execute a command in privileged EXEC mode (excludes config EXEC modes). This
|
205
|
+
# method is a wrapper for {#priv_exec}.
|
206
|
+
#
|
207
|
+
# @see #priv_exec
|
208
|
+
# @param command [String]
|
209
|
+
# @return [String] output from the command, if any
|
210
|
+
def priv_exec!(command)
|
211
|
+
must_be_connected!
|
212
|
+
send('exit', PRIV_EXEC_PROMPT) while @config_mode
|
213
|
+
priv_exec(command)
|
214
|
+
end
|
215
|
+
|
216
|
+
# A shortcut for running "show" commands.
|
217
|
+
#
|
218
|
+
# @param subcmd [String]
|
219
|
+
# @return [String]
|
220
|
+
def show(subcmd)
|
221
|
+
priv_exec('show ' + subcmd)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Execute a "show running-conifg [...]" command and load the results into a
|
225
|
+
# {Config} object.
|
226
|
+
#
|
227
|
+
# @see Config
|
228
|
+
# @param subcmd [String, nil]
|
229
|
+
# @return [Config] a top-level {Config} node with nested config
|
230
|
+
def running_config(subcmd = nil)
|
231
|
+
unless @running_config.key? subcmd
|
232
|
+
output = subcmd ? show('running-config ' + subcmd) : show('running-config')
|
233
|
+
@running_config[subcmd] = Config.new(nested_config: output)
|
234
|
+
end
|
235
|
+
@running_config[subcmd]
|
236
|
+
rescue Error::CommandError
|
237
|
+
Config.new
|
238
|
+
end
|
239
|
+
|
240
|
+
# ASA software version in `x.x(x)` format.
|
241
|
+
#
|
242
|
+
# @return [String]
|
243
|
+
def version
|
244
|
+
unless @version
|
245
|
+
# Reassemble the version string on the off chance that an interim release
|
246
|
+
# is reported in the format "x.x(x.x)" versus "x.x(x)x".
|
247
|
+
regex = /^Cisco Adaptive Security Appliance Software Version (\d+)\.(\d+)\((\d+).*?\)/
|
248
|
+
matches = regex.match(show('version'))
|
249
|
+
fail Error::VersionParseError, 'Unable to determine appliance version' unless matches
|
250
|
+
@version = '%d.%d(%d)' % matches[1..3]
|
251
|
+
end
|
252
|
+
@version
|
253
|
+
end
|
254
|
+
|
255
|
+
# Return the result of comparing the ASA software version with a list of
|
256
|
+
# expressions. Will `yield` once on success if a block is given.
|
257
|
+
#
|
258
|
+
# @example
|
259
|
+
# asa.version? '9.x', '< 9.3' do
|
260
|
+
# puts 'Running version 9.0, 9.1 or 9.2'
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# @see Util.version_match? The utility function called by this method
|
264
|
+
# @param exprs [Array<String>]
|
265
|
+
# @return [Boolean] `true` if _all_ expressions match, or `false` otherwise
|
266
|
+
def version?(*exprs)
|
267
|
+
success = Util.version_match?(version, exprs)
|
268
|
+
yield if success && block_given?
|
269
|
+
success
|
270
|
+
end
|
271
|
+
|
272
|
+
def enable!
|
273
|
+
send('enable', PASSWORD_PROMPT)
|
274
|
+
password = @enable_password ? @enable_password : @terminal.password
|
275
|
+
send(password, PRIV_EXEC_PROMPT, true)
|
276
|
+
end
|
277
|
+
private :enable!
|
278
|
+
|
279
|
+
def must_be_connected!
|
280
|
+
fail Error::NotConnectedError, 'Terminal is not connected' unless @terminal.connected?
|
281
|
+
end
|
282
|
+
private :must_be_connected!
|
283
|
+
end
|