oxidized 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.coderabbit.yaml +21 -0
- data/.github/workflows/publishdocker.yml +11 -9
- data/.github/workflows/ruby.yml +1 -3
- data/.rubocop.yml +16 -2
- data/.rubocop_todo.yml +21 -2
- data/CHANGELOG.md +76 -3
- data/README.md +2 -3
- data/Rakefile +1 -1
- data/docs/Configuration.md +40 -2
- data/docs/Creating-Models.md +129 -14
- data/docs/Docker.md +2 -1
- data/docs/Hooks.md +92 -67
- data/docs/Inputs.md +44 -12
- data/docs/Model-Notes/APC.md +72 -0
- data/docs/Model-Notes/ExaLink.md +43 -0
- data/docs/Model-Notes/Fortinet.md +75 -0
- data/docs/Model-Notes/GrandstreamHT8xx.md +8 -0
- data/docs/Model-Notes/IvantiConnectSecure.md +59 -0
- data/docs/Model-Notes/RouterOS.md +13 -0
- data/docs/Model-Notes/TrueNAS.md +23 -0
- data/docs/ModelUnitTests.md +23 -0
- data/docs/Outputs.md +18 -4
- data/docs/Release.md +7 -2
- data/docs/Ruby-API.md +86 -5
- data/docs/Supported-OS-Types.md +21 -9
- data/docs/Troubleshooting.md +1 -1
- data/extra/device2yaml.rb +2 -3
- data/extra/hooks/modelrules.rb +55 -0
- data/extra/hooks/modelrulesadvanced.rb +167 -0
- data/extra/hooks/srcipmap.rb +54 -0
- data/lib/oxidized/cli/support.rb +152 -0
- data/lib/oxidized/cli.rb +9 -0
- data/lib/oxidized/hook/githubrepo.rb +2 -1
- data/lib/oxidized/hook.rb +58 -8
- data/lib/oxidized/input/debugtext.rb +40 -0
- data/lib/oxidized/input/debugyaml.rb +82 -0
- data/lib/oxidized/input/exec.rb +1 -10
- data/lib/oxidized/input/ftp.rb +0 -17
- data/lib/oxidized/input/http.rb +39 -21
- data/lib/oxidized/input/input.rb +33 -13
- data/lib/oxidized/input/scp.rb +10 -64
- data/lib/oxidized/input/ssh.rb +36 -79
- data/lib/oxidized/input/sshbase.rb +102 -0
- data/lib/oxidized/input/telnet.rb +12 -13
- data/lib/oxidized/input/tftp.rb +7 -7
- data/lib/oxidized/model/aoscx.rb +18 -12
- data/lib/oxidized/model/aosw.rb +10 -11
- data/lib/oxidized/model/apc_aos.rb +4 -0
- data/lib/oxidized/model/apcaos.rb +39 -0
- data/lib/oxidized/model/arubainstant.rb +11 -20
- data/lib/oxidized/model/asa.rb +7 -7
- data/lib/oxidized/model/comware.rb +3 -1
- data/lib/oxidized/model/cumulus.rb +3 -3
- data/lib/oxidized/model/defacto.rb +26 -0
- data/lib/oxidized/model/dlinknextgen.rb +1 -0
- data/lib/oxidized/model/dslcommands.rb +93 -0
- data/lib/oxidized/model/dslsetup.rb +102 -0
- data/lib/oxidized/model/efos.rb +5 -5
- data/lib/oxidized/model/exalink.rb +36 -0
- data/lib/oxidized/model/fastiron.rb +2 -2
- data/lib/oxidized/model/firelinuxos.rb +1 -3
- data/lib/oxidized/model/fortigate.rb +160 -0
- data/lib/oxidized/model/fortios.rb +28 -69
- data/lib/oxidized/model/fsos.rb +1 -3
- data/lib/oxidized/model/grandstreamht8xx.rb +19 -0
- data/lib/oxidized/model/h3c.rb +1 -1
- data/lib/oxidized/model/ios.rb +23 -15
- data/lib/oxidized/model/ironware.rb +5 -3
- data/lib/oxidized/model/ivanti.rb +54 -0
- data/lib/oxidized/model/junos.rb +2 -2
- data/lib/oxidized/model/linuxgeneric.rb +4 -2
- data/lib/oxidized/model/macros.rb +60 -0
- data/lib/oxidized/model/mlnxos.rb +11 -7
- data/lib/oxidized/model/model.rb +28 -126
- data/lib/oxidized/model/ndms.rb +6 -0
- data/lib/oxidized/model/netgear.rb +5 -3
- data/lib/oxidized/model/nxos.rb +6 -3
- data/lib/oxidized/model/outputs.rb +5 -0
- data/lib/oxidized/model/perle.rb +14 -8
- data/lib/oxidized/model/routeros.rb +4 -0
- data/lib/oxidized/model/smartbyte.rb +48 -0
- data/lib/oxidized/model/tplink.rb +4 -6
- data/lib/oxidized/model/truenas.rb +63 -3
- data/lib/oxidized/model/voss.rb +3 -0
- data/lib/oxidized/model/vyos.rb +4 -1
- data/lib/oxidized/node.rb +25 -23
- data/lib/oxidized/nodes.rb +2 -0
- data/lib/oxidized/output/file.rb +7 -1
- data/lib/oxidized/output/git.rb +11 -1
- data/lib/oxidized/output/gitcrypt.rb +1 -1
- data/lib/oxidized/output/http.rb +12 -3
- data/lib/oxidized/source/csv.rb +5 -0
- data/lib/oxidized/source/jsonfile.rb +5 -0
- data/lib/oxidized/source/sql.rb +5 -0
- data/lib/oxidized/version.rb +2 -2
- data/lib/oxidized/worker.rb +36 -15
- data/lib/refinements.rb +18 -0
- data/oxidized.gemspec +28 -24
- metadata +103 -55
- data/docs/Model-Notes/APC_AOS.md +0 -65
- data/docs/Model-Notes/FortiOS.md +0 -44
data/lib/oxidized/input/scp.rb
CHANGED
|
@@ -1,54 +1,20 @@
|
|
|
1
1
|
module Oxidized
|
|
2
2
|
require 'net/ssh'
|
|
3
|
-
|
|
3
|
+
begin
|
|
4
|
+
require 'net/scp'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
raise OxidizedError, 'net/scp not found: sudo gem install net-scp'
|
|
7
|
+
end
|
|
4
8
|
require 'timeout'
|
|
5
|
-
require_relative '
|
|
9
|
+
require_relative 'sshbase'
|
|
6
10
|
|
|
7
|
-
class SCP <
|
|
11
|
+
class SCP < SSHBase
|
|
8
12
|
RESCUE_FAIL = {
|
|
9
|
-
|
|
10
|
-
Net::SSH::Disconnect,
|
|
11
|
-
Net::SSH::ConnectionTimeout
|
|
12
|
-
],
|
|
13
|
-
warn: [
|
|
14
|
-
Net::SCP::Error,
|
|
15
|
-
Net::SSH::HostKeyUnknown,
|
|
16
|
-
Net::SSH::AuthenticationFailed,
|
|
17
|
-
Timeout::Error
|
|
18
|
-
]
|
|
13
|
+
Net::SCP::Error => :warn
|
|
19
14
|
}.freeze
|
|
20
|
-
include Input::CLI
|
|
21
|
-
|
|
22
|
-
def connect(node) # rubocop:disable Naming/PredicateMethod
|
|
23
|
-
@node = node
|
|
24
|
-
@node.model.cfg['scp'].each { |cb| instance_exec(&cb) }
|
|
25
|
-
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-scp", 'w') if Oxidized.config.input.debug?
|
|
26
|
-
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
|
|
27
|
-
connected?
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def make_ssh_opts
|
|
31
|
-
secure = Oxidized.config.input.scp.secure?
|
|
32
|
-
ssh_opts = {
|
|
33
|
-
number_of_password_prompts: 0,
|
|
34
|
-
verify_host_key: secure ? :always : :never,
|
|
35
|
-
append_all_supported_algorithms: true,
|
|
36
|
-
password: @node.auth[:password],
|
|
37
|
-
timeout: @node.timeout,
|
|
38
|
-
port: (vars(:ssh_port) || 22).to_i,
|
|
39
|
-
forward_agent: false
|
|
40
|
-
}
|
|
41
15
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ssh_logger.level = Oxidized.config.input.debug? ? :debug : :fatal
|
|
45
|
-
ssh_opts[:logger] = ssh_logger
|
|
46
|
-
|
|
47
|
-
ssh_opts
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def connected?
|
|
51
|
-
@ssh && (not @ssh.closed?)
|
|
16
|
+
def self.rescue_fail
|
|
17
|
+
super.merge(RESCUE_FAIL)
|
|
52
18
|
end
|
|
53
19
|
|
|
54
20
|
def cmd(file)
|
|
@@ -57,25 +23,5 @@ module Oxidized
|
|
|
57
23
|
@ssh.scp.download!(file)
|
|
58
24
|
end
|
|
59
25
|
end
|
|
60
|
-
|
|
61
|
-
def send(my_proc)
|
|
62
|
-
my_proc.call
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def output
|
|
66
|
-
""
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def disconnect
|
|
72
|
-
Timeout.timeout(@node.timeout) do
|
|
73
|
-
@ssh.close
|
|
74
|
-
end
|
|
75
|
-
rescue Timeout::Error
|
|
76
|
-
logger.debug "#{@node.name} timed out while disconnecting"
|
|
77
|
-
ensure
|
|
78
|
-
@log.close if Oxidized.config.input.debug?
|
|
79
|
-
end
|
|
80
26
|
end
|
|
81
27
|
end
|
data/lib/oxidized/input/ssh.rb
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
|
+
require 'timeout'
|
|
2
|
+
require_relative 'sshbase'
|
|
3
|
+
require_relative 'debugyaml'
|
|
4
|
+
require_relative 'debugtext'
|
|
5
|
+
|
|
1
6
|
module Oxidized
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require 'oxidized/input/cli'
|
|
6
|
-
class SSH < Input
|
|
7
|
+
class SSH < SSHBase
|
|
8
|
+
class NoShell < OxidizedError; end
|
|
9
|
+
|
|
7
10
|
RESCUE_FAIL = {
|
|
8
|
-
|
|
9
|
-
Net::SSH::Disconnect
|
|
10
|
-
],
|
|
11
|
-
warn: [
|
|
12
|
-
RuntimeError,
|
|
13
|
-
Net::SSH::AuthenticationFailed
|
|
14
|
-
]
|
|
11
|
+
RuntimeError => :warn
|
|
15
12
|
}.freeze
|
|
16
|
-
include Input::CLI
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
def self.rescue_fail
|
|
15
|
+
super.merge(RESCUE_FAIL)
|
|
16
|
+
end
|
|
19
17
|
|
|
20
18
|
def connect(node) # rubocop:disable Naming/PredicateMethod
|
|
21
19
|
@node = node
|
|
22
20
|
@output = String.new('')
|
|
23
21
|
@pty_options = { term: "vt100" }
|
|
24
22
|
@node.model.cfg['ssh'].each { |cb| instance_exec(&cb) }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
logger.debug "I/O Debuging to #{logfile}"
|
|
29
|
-
end
|
|
23
|
+
|
|
24
|
+
@yaml_debug = DebugYAML.new(Oxidized.config.input.debug, @node, config_name)
|
|
25
|
+
@text_debug = DebugText.new(Oxidized.config.input.debug, @node, config_name)
|
|
30
26
|
|
|
31
27
|
logger.debug "Connecting to #{@node.name}"
|
|
32
28
|
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
|
|
@@ -41,30 +37,31 @@ module Oxidized
|
|
|
41
37
|
connected?
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
def connected?
|
|
45
|
-
@ssh && (not @ssh.closed?)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
40
|
def cmd(cmd, expect = node.prompt)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@log.flush
|
|
41
|
+
unless cmd.is_a?(String)
|
|
42
|
+
logger.error "cmd must be a String (#{cmd.class}): #{cmd.inspect} @ #{node.name}"
|
|
43
|
+
raise ArgumentError, "cmd must be a String"
|
|
53
44
|
end
|
|
45
|
+
logger.debug "Sending '#{cmd.dump}' @ #{node.name} with expect: #{expect.inspect}"
|
|
54
46
|
cmd_output = if @exec
|
|
47
|
+
@yaml_debug&.send_data(cmd)
|
|
48
|
+
@text_debug&.send_data(cmd)
|
|
55
49
|
@ssh.exec! cmd
|
|
56
50
|
else
|
|
57
51
|
cmd_shell(cmd, expect).gsub("\r\n", "\n")
|
|
58
52
|
end
|
|
53
|
+
|
|
54
|
+
# only logging @exec as cmd_shell is handled in the ssh loop
|
|
55
|
+
@yaml_debug&.receive_data(cmd_output) if @exec
|
|
56
|
+
@text_debug&.receive_data(cmd_output) if @exec
|
|
57
|
+
|
|
59
58
|
# Make sure we return a String
|
|
60
59
|
cmd_output.to_s
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
def send(data)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@log.flush
|
|
67
|
-
end
|
|
63
|
+
@yaml_debug&.send_data(data)
|
|
64
|
+
@text_debug&.send_data(data)
|
|
68
65
|
@ses.send_data data
|
|
69
66
|
end
|
|
70
67
|
|
|
@@ -76,6 +73,7 @@ module Oxidized
|
|
|
76
73
|
|
|
77
74
|
private
|
|
78
75
|
|
|
76
|
+
# We need a specific disconnect for SSH in shell mode, see issue #3725
|
|
79
77
|
def disconnect
|
|
80
78
|
disconnect_cli
|
|
81
79
|
# if disconnect does not disconnect us, give up after timeout
|
|
@@ -86,17 +84,16 @@ module Oxidized
|
|
|
86
84
|
rescue Timeout::Error
|
|
87
85
|
logger.debug "#{@node.name} timed out while disconnecting"
|
|
88
86
|
ensure
|
|
89
|
-
@
|
|
87
|
+
@yaml_debug&.close
|
|
88
|
+
@text_debug&.close
|
|
90
89
|
(@ssh.close rescue true) unless @ssh.closed? # rubocop:disable Style/RedundantParentheses
|
|
91
90
|
end
|
|
92
91
|
|
|
93
92
|
def shell_open(ssh)
|
|
94
93
|
@ses = ssh.open_channel do |ch|
|
|
95
94
|
ch.on_data do |_ch, data|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@log.flush
|
|
99
|
-
end
|
|
95
|
+
@yaml_debug&.receive_data(data)
|
|
96
|
+
@text_debug&.receive_data(data)
|
|
100
97
|
@output << data
|
|
101
98
|
@output = @node.model.expects @output
|
|
102
99
|
end
|
|
@@ -118,6 +115,9 @@ module Oxidized
|
|
|
118
115
|
|
|
119
116
|
def cmd_shell(cmd, expect_re)
|
|
120
117
|
@output = String.new('')
|
|
118
|
+
|
|
119
|
+
@yaml_debug&.send_data(cmd + newline)
|
|
120
|
+
@text_debug&.send_data(cmd + newline)
|
|
121
121
|
@ses.send_data cmd + newline
|
|
122
122
|
@ses.process
|
|
123
123
|
expect expect_re if expect_re
|
|
@@ -137,48 +137,5 @@ module Oxidized
|
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
|
-
|
|
141
|
-
def make_ssh_opts
|
|
142
|
-
secure = Oxidized.config.input.ssh.secure?
|
|
143
|
-
ssh_opts = {
|
|
144
|
-
number_of_password_prompts: 0,
|
|
145
|
-
keepalive: vars(:ssh_no_keepalive) ? false : true,
|
|
146
|
-
verify_host_key: secure ? :always : :never,
|
|
147
|
-
append_all_supported_algorithms: true,
|
|
148
|
-
password: @node.auth[:password],
|
|
149
|
-
timeout: @node.timeout,
|
|
150
|
-
port: (vars(:ssh_port) || 22).to_i,
|
|
151
|
-
forward_agent: false
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
auth_methods = vars(:auth_methods) || %w[none publickey password]
|
|
155
|
-
ssh_opts[:auth_methods] = auth_methods
|
|
156
|
-
logger.debug "AUTH METHODS::#{auth_methods}"
|
|
157
|
-
|
|
158
|
-
ssh_opts[:proxy] = make_ssh_proxy_command(vars(:ssh_proxy), vars(:ssh_proxy_port), secure) if vars(:ssh_proxy)
|
|
159
|
-
|
|
160
|
-
ssh_opts[:keys] = [vars(:ssh_keys)].flatten if vars(:ssh_keys)
|
|
161
|
-
ssh_opts[:kex] = vars(:ssh_kex).split(/,\s*/) if vars(:ssh_kex)
|
|
162
|
-
ssh_opts[:encryption] = vars(:ssh_encryption).split(/,\s*/) if vars(:ssh_encryption)
|
|
163
|
-
ssh_opts[:host_key] = vars(:ssh_host_key).split(/,\s*/) if vars(:ssh_host_key)
|
|
164
|
-
ssh_opts[:hmac] = vars(:ssh_hmac).split(/,\s*/) if vars(:ssh_hmac)
|
|
165
|
-
|
|
166
|
-
# Use our logger for Net::SSH
|
|
167
|
-
ssh_logger = SemanticLogger[Net::SSH]
|
|
168
|
-
ssh_logger.level = Oxidized.config.input.debug? ? :debug : :fatal
|
|
169
|
-
ssh_opts[:logger] = ssh_logger
|
|
170
|
-
|
|
171
|
-
ssh_opts
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def make_ssh_proxy_command(proxy_host, proxy_port, secure)
|
|
175
|
-
return nil unless !proxy_host.nil? && !proxy_host.empty?
|
|
176
|
-
|
|
177
|
-
proxy_command = "ssh "
|
|
178
|
-
proxy_command += "-o StrictHostKeyChecking=no " unless secure
|
|
179
|
-
proxy_command += "-p #{proxy_port} " if proxy_port
|
|
180
|
-
proxy_command += "#{proxy_host} -W [%h]:%p"
|
|
181
|
-
Net::SSH::Proxy::Command.new(proxy_command)
|
|
182
|
-
end
|
|
183
140
|
end
|
|
184
141
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Oxidized
|
|
2
|
+
require 'net/ssh'
|
|
3
|
+
require 'net/ssh/proxy/command'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
|
|
6
|
+
class SSHBase < Input
|
|
7
|
+
RESCUE_FAIL = {
|
|
8
|
+
Net::SSH::Disconnect => :debug,
|
|
9
|
+
Net::SSH::ConnectionTimeout => :debug,
|
|
10
|
+
Net::SSH::AuthenticationFailed => :warn,
|
|
11
|
+
Net::SSH::HostKeyUnknown => :warn
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def self.rescue_fail
|
|
15
|
+
super.merge(RESCUE_FAIL)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def connect(node) # rubocop:disable Naming/PredicateMethod
|
|
19
|
+
@node = node
|
|
20
|
+
@node.model.cfg[config_name].each { |cb| instance_exec(&cb) }
|
|
21
|
+
logger.debug "Connecting to #{@node.name}"
|
|
22
|
+
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
|
|
23
|
+
connected?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def connected?
|
|
27
|
+
@ssh && (not @ssh.closed?)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def make_ssh_opts
|
|
31
|
+
ssh_opts = {
|
|
32
|
+
number_of_password_prompts: 0,
|
|
33
|
+
keepalive: vars(:ssh_no_keepalive) ? false : true,
|
|
34
|
+
verify_host_key: must_secure? ? :always : :never,
|
|
35
|
+
append_all_supported_algorithms: true,
|
|
36
|
+
password: @node.auth[:password],
|
|
37
|
+
timeout: @node.timeout,
|
|
38
|
+
port: (vars(:ssh_port) || 22).to_i,
|
|
39
|
+
forward_agent: false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
auth_methods = vars(:auth_methods) || %w[none publickey password]
|
|
43
|
+
ssh_opts[:auth_methods] = auth_methods
|
|
44
|
+
logger.debug "AUTH METHODS::#{auth_methods}"
|
|
45
|
+
|
|
46
|
+
if vars(:ssh_proxy)
|
|
47
|
+
ssh_opts[:proxy] = make_ssh_proxy_command(
|
|
48
|
+
vars(:ssh_proxy), vars(:ssh_proxy_port), must_secure?
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
ssh_opts[:keys] = [vars(:ssh_keys)].flatten if vars(:ssh_keys)
|
|
52
|
+
ssh_opts[:kex] = vars(:ssh_kex).split(/,\s*/) if vars(:ssh_kex)
|
|
53
|
+
ssh_opts[:encryption] = vars(:ssh_encryption).split(/,\s*/) if vars(:ssh_encryption)
|
|
54
|
+
ssh_opts[:host_key] = vars(:ssh_host_key).split(/,\s*/) if vars(:ssh_host_key)
|
|
55
|
+
ssh_opts[:hmac] = vars(:ssh_hmac).split(/,\s*/) if vars(:ssh_hmac)
|
|
56
|
+
|
|
57
|
+
# Use our logger for Net::SSH
|
|
58
|
+
ssh_logger = SemanticLogger[Net::SSH]
|
|
59
|
+
config_debug = Oxidized.config.input.debug
|
|
60
|
+
if config_debug == true ||
|
|
61
|
+
(config_debug.is_a?(String) && config_debug.downcase.include?('library'))
|
|
62
|
+
ssh_logger.level = :debug
|
|
63
|
+
else
|
|
64
|
+
ssh_logger.level = :fatal
|
|
65
|
+
end
|
|
66
|
+
ssh_opts[:logger] = ssh_logger
|
|
67
|
+
|
|
68
|
+
ssh_opts
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def must_secure?
|
|
72
|
+
Oxidized.config.input[config_name].secure? == true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def make_ssh_proxy_command(proxy_host, proxy_port, secure)
|
|
76
|
+
return nil unless !proxy_host.nil? && !proxy_host.empty?
|
|
77
|
+
|
|
78
|
+
proxy_command = "ssh "
|
|
79
|
+
proxy_command += "-o StrictHostKeyChecking=no " unless secure
|
|
80
|
+
proxy_command += "-p #{proxy_port} " if proxy_port
|
|
81
|
+
proxy_command += "#{proxy_host} -W [%h]:%p"
|
|
82
|
+
Net::SSH::Proxy::Command.new(proxy_command)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def disconnect
|
|
86
|
+
disconnect_cli
|
|
87
|
+
Timeout.timeout(@node.timeout) do
|
|
88
|
+
@ssh.close
|
|
89
|
+
end
|
|
90
|
+
rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError => e
|
|
91
|
+
logger.debug 'The other side closed the connection while ' \
|
|
92
|
+
"disconnecting, raising #{e.class} with #{e.message}"
|
|
93
|
+
rescue Timeout::Error
|
|
94
|
+
logger.debug "#{@node.name} timed out while disconnecting"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Methods to implement in subclasses
|
|
98
|
+
def cmd(**_args)
|
|
99
|
+
raise NotImplementedError, "Subclasses must implement cmd"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
require_relative 'debugtext'
|
|
2
|
+
|
|
1
3
|
module Oxidized
|
|
2
4
|
require 'net/telnet'
|
|
3
|
-
require 'oxidized/input/cli'
|
|
4
5
|
class Telnet < Input
|
|
5
|
-
RESCUE_FAIL = {}.freeze
|
|
6
|
-
include Input::CLI
|
|
7
|
-
|
|
8
6
|
attr_reader :telnet
|
|
9
7
|
|
|
10
8
|
def connect(node) # rubocop:disable Naming/PredicateMethod
|
|
11
9
|
@node = node
|
|
12
10
|
@timeout = @node.timeout
|
|
13
11
|
@node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
|
|
14
|
-
|
|
12
|
+
|
|
13
|
+
@text_debug = DebugText.new(Oxidized.config.input.debug, @node, config_name)
|
|
14
|
+
|
|
15
15
|
port = vars(:telnet_port) || 23
|
|
16
16
|
|
|
17
17
|
telnet_opts = {
|
|
@@ -19,7 +19,7 @@ module Oxidized
|
|
|
19
19
|
'Port' => port.to_i,
|
|
20
20
|
'Timeout' => @timeout,
|
|
21
21
|
'Model' => @node.model,
|
|
22
|
-
'Log' => @
|
|
22
|
+
'Log' => @text_debug
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
@telnet = Net::Telnet.new telnet_opts
|
|
@@ -42,12 +42,14 @@ module Oxidized
|
|
|
42
42
|
# create a string to be passed to oxidized_expect and modified _there_
|
|
43
43
|
# default to a single space so it shouldn't be coerced to nil by any models.
|
|
44
44
|
out = String(' ')
|
|
45
|
+
@text_debug&.send_data(cmd_str)
|
|
45
46
|
@telnet.puts(cmd_str)
|
|
46
47
|
@telnet.oxidized_expect(timeout: @timeout, expect: expect, out: out)
|
|
47
48
|
out
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
def send(data)
|
|
52
|
+
@text_debug&.send_data(data)
|
|
51
53
|
@telnet.write data
|
|
52
54
|
end
|
|
53
55
|
|
|
@@ -67,7 +69,7 @@ module Oxidized
|
|
|
67
69
|
rescue Errno::ECONNRESET, IOError
|
|
68
70
|
# This exception is intented and therefore not handled here
|
|
69
71
|
ensure
|
|
70
|
-
@
|
|
72
|
+
@text_debug&.close
|
|
71
73
|
(@telnet.close rescue true) unless @telnet.sock.closed? # rubocop:disable Style/RedundantParentheses
|
|
72
74
|
end
|
|
73
75
|
end
|
|
@@ -80,8 +82,8 @@ module Net
|
|
|
80
82
|
attr_reader :output
|
|
81
83
|
|
|
82
84
|
def oxidized_expect(options)
|
|
83
|
-
model
|
|
84
|
-
|
|
85
|
+
model = @options["Model"]
|
|
86
|
+
text_debug = @options["Log"]
|
|
85
87
|
|
|
86
88
|
expects = [options[:expect]].flatten
|
|
87
89
|
time_out = options[:timeout] || @options["Timeout"]
|
|
@@ -107,10 +109,7 @@ module Net
|
|
|
107
109
|
buf = preprocess(c)
|
|
108
110
|
rest = ''
|
|
109
111
|
end
|
|
110
|
-
|
|
111
|
-
@log.print buf
|
|
112
|
-
@log.flush
|
|
113
|
-
end
|
|
112
|
+
text_debug&.receive_data buf
|
|
114
113
|
line += buf
|
|
115
114
|
line = model.expects line
|
|
116
115
|
# match is a regexp object. we need to return that for logins to work.
|
data/lib/oxidized/input/tftp.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module Oxidized
|
|
2
2
|
require 'stringio'
|
|
3
|
-
require_relative 'cli'
|
|
4
|
-
|
|
5
3
|
begin
|
|
6
4
|
require 'net/tftp'
|
|
7
5
|
rescue LoadError
|
|
@@ -9,14 +7,19 @@ module Oxidized
|
|
|
9
7
|
end
|
|
10
8
|
|
|
11
9
|
class TFTP < Input
|
|
12
|
-
|
|
10
|
+
RESCUE_FAIL = {
|
|
11
|
+
Net::TFTPTimeout => :warn
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def self.rescue_fail
|
|
15
|
+
super.merge(RESCUE_FAIL)
|
|
16
|
+
end
|
|
13
17
|
|
|
14
18
|
# TFTP utilizes UDP, there is not a connection. We simply specify an IP and send/receive data.
|
|
15
19
|
def connect(node)
|
|
16
20
|
@node = node
|
|
17
21
|
|
|
18
22
|
@node.model.cfg['tftp'].each { |cb| instance_exec(&cb) }
|
|
19
|
-
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-tftp", 'w') if Oxidized.config.input.debug?
|
|
20
23
|
@tftp = Net::TFTP.new @node.ip
|
|
21
24
|
end
|
|
22
25
|
|
|
@@ -32,9 +35,6 @@ module Oxidized
|
|
|
32
35
|
|
|
33
36
|
def disconnect
|
|
34
37
|
# TFTP uses UDP, there is no connection to close
|
|
35
|
-
true
|
|
36
|
-
ensure
|
|
37
|
-
@log.close if Oxidized.config.input.debug?
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
data/lib/oxidized/model/aoscx.rb
CHANGED
|
@@ -22,9 +22,11 @@ class Aoscx < Oxidized::Model
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
cmd :secret do |cfg|
|
|
25
|
+
cfg.gsub! /^(user .* group .*(?: ciphertext)?) \S+/, '\\1 <secret hidden>'
|
|
25
26
|
cfg.gsub! /^(snmp-server community) \S+(.*)/, '\\1 <secret hidden> \\2'
|
|
26
27
|
cfg.gsub! /^(snmp-server host \S+) \S+(.*)/, '\\1 <secret hidden> \\2'
|
|
27
|
-
cfg.gsub! /^(
|
|
28
|
+
cfg.gsub! /^(snmpv3 user).*?(auth (?:md5|sha(?:\d{1,3})?) auth-pass ciphertext).*?(priv (?:des|aes(?:\d{1,3})?) priv-pass ciphertext).*/, '\\1 <user> \\2 <auth-pass> \\3 <priv-pass>'
|
|
29
|
+
cfg.gsub! /^(radius-server host \S+ key(?: ciphertext)?) \S+ (.*)/, '\\1 <secret hidden> \\2'
|
|
28
30
|
cfg.gsub! /^(radius-server key).*/, '\\1 <configuration removed>'
|
|
29
31
|
cfg.gsub! /^(tacacs-server host \S+ key) \S+(.*)/, '\\1 <secret hidden> \\2'
|
|
30
32
|
cfg.gsub! /^(tacacs-server key).*/, '\\1 <secret hidden>'
|
|
@@ -35,22 +37,23 @@ class Aoscx < Oxidized::Model
|
|
|
35
37
|
comment cfg
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
header + content
|
|
45
|
-
end
|
|
40
|
+
def with_section(cfg, section, &block)
|
|
41
|
+
cfg.sub!(/(show environment #{section}.*?-{10,}\n)(.*?)(?=\nshow environment|\z)/m) do
|
|
42
|
+
header = ::Regexp.last_match(1)
|
|
43
|
+
content = ::Regexp.last_match(2)
|
|
44
|
+
block.call(content) if block_given?
|
|
45
|
+
header + content
|
|
46
46
|
end
|
|
47
|
+
end
|
|
47
48
|
|
|
49
|
+
cmd 'show environment' do |cfg|
|
|
48
50
|
with_section(cfg, 'fan') do |content|
|
|
49
|
-
content.gsub!(/^(
|
|
51
|
+
content.gsub!(/^((?:\S+ +){3})(slow |normal|medium|fast |max |N\/A ) (.*?)\d+ +$/, '\\1<speed> \\3<rpm>')
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
with_section(cfg, 'power-consumption') do |content|
|
|
53
55
|
content.gsub!(/^(.*?) (?:\d+\.\d+ +)+\d+\.\d+$/, '\\1 <power hidden>')
|
|
56
|
+
content.gsub!(/^(Total Power Consumption +)\d+\.\d+$/, '\\1<power hidden>')
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
with_section(cfg, 'power-allocation') do |content|
|
|
@@ -58,7 +61,7 @@ class Aoscx < Oxidized::Model
|
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
with_section(cfg, 'temperature') do |content|
|
|
61
|
-
content.gsub!(/^(.*)
|
|
64
|
+
content.gsub!(/^(.*) -?\d+\.\d+ C (.*)$/, '\\1 <hidden>\\2')
|
|
62
65
|
end
|
|
63
66
|
comment cfg
|
|
64
67
|
end
|
|
@@ -71,7 +74,10 @@ class Aoscx < Oxidized::Model
|
|
|
71
74
|
comment cfg
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
cmd 'show system
|
|
77
|
+
cmd 'show system' do |cfg|
|
|
78
|
+
cfg = cfg.reject_lines [
|
|
79
|
+
"Up Time", "CPU", "Memory", /Pkts .x/, "Lowest", "Missed"
|
|
80
|
+
]
|
|
75
81
|
comment cfg
|
|
76
82
|
end
|
|
77
83
|
|
data/lib/oxidized/model/aosw.rb
CHANGED
|
@@ -48,8 +48,11 @@ class AOSW < Oxidized::Model
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
cmd 'show version' do |cfg|
|
|
51
|
-
cfg = cfg.
|
|
52
|
-
|
|
51
|
+
cfg = cfg.reject_lines [
|
|
52
|
+
/(Switch|AP) uptime/i,
|
|
53
|
+
/Reboot Time and Cause/i
|
|
54
|
+
]
|
|
55
|
+
rstrip_cfg comment cfg
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
cmd 'show inventory' do |cfg|
|
|
@@ -77,15 +80,11 @@ class AOSW < Oxidized::Model
|
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
cmd 'show running-config' do |cfg|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
out << line.strip
|
|
86
|
-
end
|
|
87
|
-
out = out.join "\n"
|
|
88
|
-
out << "\n"
|
|
83
|
+
cfg = cfg.reject_lines [
|
|
84
|
+
/^controller config \d+$/,
|
|
85
|
+
/^Building Configuration/
|
|
86
|
+
]
|
|
87
|
+
rstrip_cfg cfg
|
|
89
88
|
end
|
|
90
89
|
|
|
91
90
|
cfg :telnet do
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
class Apc_aos < Oxidized::Model # rubocop:disable Naming/ClassAndModuleCamelCase
|
|
2
2
|
using Refinements
|
|
3
3
|
|
|
4
|
+
comment '; '
|
|
5
|
+
|
|
4
6
|
cmd 'config.ini' do |cfg|
|
|
7
|
+
logger.warn "Apc_aos is deprecated, use ApcAos instead."
|
|
8
|
+
|
|
5
9
|
cfg.gsub!(/^; Configuration file, generated on.*\n/, '')
|
|
6
10
|
cfg
|
|
7
11
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class ApcAos < Oxidized::Model
|
|
2
|
+
using Refinements
|
|
3
|
+
|
|
4
|
+
# Prompt can be short (apc>) or long (username@apc>)
|
|
5
|
+
prompt /^(:?\S+@)?apc>/
|
|
6
|
+
comment '; '
|
|
7
|
+
|
|
8
|
+
def clean(cfg)
|
|
9
|
+
cfg = cfg.cut_both(2, 1)
|
|
10
|
+
cfg.gsub("\r", "")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
cmd 'about', input: :ssh do |cfg|
|
|
14
|
+
cfg = clean(cfg)
|
|
15
|
+
cfg = cfg.reject_lines [/^Management Uptime: /, /^Date: /, /^Time: /]
|
|
16
|
+
comment cfg
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
cmd 'upsabout', input: :ssh do |cfg|
|
|
20
|
+
cfg = clean(cfg)
|
|
21
|
+
comment cfg
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
cmd 'detstatus -ss', input: :ssh do |cfg|
|
|
25
|
+
cfg = clean(cfg)
|
|
26
|
+
comment cfg
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
cmd 'config.ini', input: %i[scp ftp] do |cfg|
|
|
30
|
+
cfg = cfg.reject_lines [/^; Configuration file, generated on /]
|
|
31
|
+
"; ========== config.ini ==========\n" + cfg
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
inputs [:ssh, %i[scp ftp]]
|
|
35
|
+
|
|
36
|
+
cfg :ssh do
|
|
37
|
+
pre_logout 'exit'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -31,30 +31,21 @@ class ArubaInstant < Oxidized::Model
|
|
|
31
31
|
|
|
32
32
|
# get software version
|
|
33
33
|
cmd 'show version' do |cfg|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
out += line
|
|
41
|
-
end
|
|
42
|
-
comment out
|
|
34
|
+
cfg = cfg.reject_lines [
|
|
35
|
+
/^(Switch|AP) uptime is /,
|
|
36
|
+
/^Reboot Time and Cause/
|
|
37
|
+
]
|
|
38
|
+
comment cfg
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
# Get serial number
|
|
46
42
|
cmd 'show activate status' do |cfg|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
next if line =~ /^Cloud Activation Key/
|
|
54
|
-
|
|
55
|
-
out += line
|
|
56
|
-
end
|
|
57
|
-
comment out + "\n"
|
|
43
|
+
cfg = cfg.reject_lines [
|
|
44
|
+
/^Activate /,
|
|
45
|
+
/^Provision interval/,
|
|
46
|
+
/^Cloud Activation Key/
|
|
47
|
+
]
|
|
48
|
+
comment cfg + "\n"
|
|
58
49
|
end
|
|
59
50
|
|
|
60
51
|
# Get controlled WLAN-AP
|