diff options
43 files changed, 1070 insertions, 191 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e1422c --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Used by dotenv library to load environment variables. +# .env + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc diff --git a/CHANGELOG.md b/CHANGELOG.md index c482508..838d77d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ +# 0.19.0 +- FEATURE: allow setting ssh_keys (not relying on openssh config) (@denvera) +- FEATURE: fujitsupy model (@stokbaek) +- FEATURE: fiberdriver model (@emjemj) +- FEATURE: hpbladesystems model (@flokli) +- FEATURE: planetsgs model (@flokli) +- FEATURE: trango model (@rfdrake) +- FEATURE: casa model (@rfdrake) +- FEATURE: dlink model (@rfdrake) +- FEATURE: hatteras model (@rfdrake) +- FEATURE: ability to ignore SSL certs in http (@laf) +- FEATURE: awsns hooks, publish messages to AWS SNS topics (@natm) +- BUGFIX: pfsense, dnos, powerconnect, ciscosmb, eos, aosw + +# 0.18.0 +- FEATURE: APC model (by @davromaniak ) +- BUGFIX: ironware, aosw +- BUGFIX: interpolate nil, false, true for node vars too + +# 0 17.0 +- FEATURE: "nil", "false" and "true" in source (e.g. router.db) are interpeted as nil, false, true. Empty is now always considered empty string, instead of in some cases nil and some cases empty string. +- FEATURE: support tftp as input model (@MajesticFalcon) +- FEATURE: add alvarion model (@MajesticFalcon) +- FEATURE: detect if ssh wants password terminal/CLI prompt or not +- FEATURE: node (group, model, username, password) resolution refactoring, supports wider range of use-cases +- BUGFIX: fetch for file output (@danilopopeye) +- BUGFIX: net-ssh version specification +- BUGFIX: routeros, catos, pfsense + # 0.16.3 +- FEATURE: pfsense support (by @stokbaek) - BUGFIX: cumulus prompt not working with default switch configs (by @nertwork) +- BUGFIX: disconnect ssh when prompt wasn't found (by @andir) +- BUGFIX: saos, asa, acos, timos updates, cumulus + +# 0.16.2 +- BUGFIX: when not using git (by @danilopopeye) +- BUGFIX: screenos update # 0.16.2 - BUGFIX: when not using git (by @danilopopeye) @@ -3,7 +3,7 @@ MAINTAINER Samer Abdel-Hafez <sam@arahant.net> RUN add-apt-repository ppa:brightbox/ruby-ng && \ apt-get update && \ - apt-get install -y ruby2.1 ruby2.1-dev libsqlite3-dev libssl-dev pkg-config make cmake + apt-get install -y ruby2.3 ruby2.3-dev libsqlite3-dev libssl-dev pkg-config make cmake libssh2-1-dev RUN gem install oxidized oxidized-web --no-ri --no-rdoc diff --git a/Gemfile.lock b/Gemfile.lock index 1188627..ab52715 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,10 @@ PATH remote: . specs: - oxidized (0.14.3) + oxidized (0.18.0) asetus (~> 0.1) - net-ssh (>= 3.0.0, < 3.1) + net-ssh (~> 3.0.2) + net-telnet rugged (~> 0.21, >= 0.21.4) slop (~> 3.5) @@ -14,16 +15,17 @@ GEM coderay (1.1.0) metaclass (0.0.4) method_source (0.8.2) - minitest (5.8.3) + minitest (5.9.0) mocha (1.1.0) metaclass (~> 0.0.1) net-ssh (3.0.2) + net-telnet (0.1.1) pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - rake (10.4.2) - rugged (0.24.0) + rake (10.5.0) + rugged (0.23.3) slop (3.6.0) PLATFORMS @@ -38,4 +40,4 @@ DEPENDENCIES rake (~> 10.0) BUNDLED WITH - 1.12.4 + 1.11.2 @@ -1,6 +1,4 @@ -# Oxidized [![Build Status](https://travis-ci.org/Shopify/oxidized.svg)](https://travis-ci.org/Shopify/oxidized) - -[![Gem Version](https://badge.fury.io/rb/oxidized.svg)](http://badge.fury.io/rb/oxidized) +# Oxidized [![Build Status](https://travis-ci.org/Shopify/oxidized.svg)](https://travis-ci.org/Shopify/oxidized) [![Gem Version](https://badge.fury.io/rb/oxidized.svg)](http://badge.fury.io/rb/oxidized) [![Join the chat at https://gitter.im/oxidized/Lobby](https://badges.gitter.im/oxidized/Lobby.svg)](https://gitter.im/oxidized/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Oxidized is a network device configuration backup tool. It's a RANCID replacement! @@ -29,13 +27,16 @@ Oxidized is a network device configuration backup tool. It's a RANCID replacemen * [Privileged mode](#privileged-mode) * [Disabling SSH exec channels](#disabling-ssh-exec-channels) * [Source: CSV](#source-csv) - * [Source: SQLite](#source-sqlite) + * [Source: SQL](#source-sql) + * [Source: SQLite](#source-sqlite) + * [Source: Mysql](#source-mysql) * [Source: HTTP](#source-http) * [Output: GIT](#output-git) * [Output: HTTP](#output-http) * [Output: File](#output-file) * [Output types](#output-types) * [Advanced Configuration](#advanced-configuration) + * [Advanced Group Configuration](#advanced-group-configuration) 7. [Ruby API](#ruby-api) * [Input](#input) * [Output](#output) @@ -43,99 +44,118 @@ Oxidized is a network device configuration backup tool. It's a RANCID replacemen * [Model](#model) # Supported OS types + * Vendor + * OS model * A10 Networks - * ACOS + * [ACOS](lib/oxidized/model/acos.rb) * Alcatel-Lucent - * AOS - * AOS7 - * ISAM + * [AOS](lib/oxidized/model/aos.rb) + * [AOS7](lib/oxidized/model/aos7.rb) + * [ISAM](lib/oxidized/model/isam.rb) * Wireless + * Alvarion + * [BreezeACCESS](lib/oxidized/model/alvarion.rb) + * APC + * [AOS](lib/oxidized/model/apc_aos.rb) * Arista - * EOS + * [EOS](lib/oxidized/model/eos.rb) * Arris - * C4CMTS + * [C4CMTS](lib/oxidized/model/c4cmts.rb) * Aruba - * AOSW + * [AOSW](lib/oxidized/model/aosw.rb) * Brocade - * FabricOS - * Ironware - * NOS (Network Operating System) - * Vyatta - * 6910 + * [FabricOS](lib/oxidized/model/fabricos.rb) + * [Ironware](lib/oxidized/model/ironware.rb) + * [NOS (Network Operating System)](lib/oxidized/model/nos.rb) + * [Vyatta](lib/oxidized/model/vyatta.rb) + * [6910](lib/oxidized/model/br6910.rb) + * Casa + * [Casa](lib/oxidized/model/casa.rb) * Check Point - * GaiaOS + * [GaiaOS](lib/oxidized/model/gaiaos.rb) * Ciena - * SOAS + * [SOAS](lib/oxidized/model/saos.rb) * Cisco - * AireOS - * ASA - * CatOS - * IOS - * IOSXR - * NXOS - * SMB (Nikola series) + * [AireOS](lib/oxidized/model/aireos.rb) + * [ASA](lib/oxidized/model/asa.rb) + * [CatOS](lib/oxidized/model/catos.rb) + * [IOS](lib/oxidized/model/ios.rb) + * [IOSXR](lib/oxidized/model/iosxr.rb) + * [NXOS](lib/oxidized/model/nxos.rb) + * [SMB (Nikola series)](lib/oxidized/model/ciscosmb.rb) * Citrix - * NetScaler (Virtual Applicance) + * [NetScaler (Virtual Applicance)](lib/oxidized/model/netscaler.rb) * Coriant (former Tellabs) - * TMOS (8800) - * 8600 + * [TMOS (8800)](lib/oxidized/model/corianttmos.rb) + * [8600](lib/oxidized/model/coriant8600.rb) * Cumulus - * Linux + * [Linux](lib/oxidized/model/cumulus.rb) * DataCom - * DmSwitch 3000 + * [DmSwitch 3000](lib/oxidized/model/datacom.rb) * DELL - * PowerConnect - * AOSW + * [PowerConnect](lib/oxidized/model/powerconnect.rb) + * [AOSW](lib/oxidized/model/aosw.rb) + * D-Link + * [D-Link](lib/oxidized/model/dlink.rb) * Ericsson/Redback - * IPOS (former SEOS) + * [IPOS (former SEOS)](lib/oxidized/model/ipos.rb) * Extreme Networks - * XOS - * WM + * [XOS](lib/oxidized/model/xos.rb) + * [WM](lib/oxidized/model/mtrlrfs.rb) * F5 - * TMOS + * [TMOS](lib/oxidized/model/tmos.rb) * Force10 - * DNOS - * FTOS + * [DNOS](lib/oxidized/model/dnos.rb) + * [FTOS](lib/oxidized/model/ftos.rb) * FortiGate - * FortiOS + * [FortiOS](lib/oxidized/model/fortios.rb) + * Fujitsu + * [PRIMERGY Blade switch 1/10Gbe](lib/oxidized/model/fujitsupy.rb) + * Hatteras + * [Hatteras](lib/oxidized/model/hatteras.rb) * HP - * Comware (HP A-series, H3C, 3Com) - * Procurve + * [Comware (HP A-series, H3C, 3Com)](lib/oxidized/model/comware.rb) + * [Procurve](lib/oxidized/model/procurve.rb) + * [BladeSystem (Onboard Administrator)](lib/oxidized/model/hpebladesystem.rb) * Huawei - * VRP + * [VRP](lib/oxidized/model/vrp.rb) * Juniper - * JunOS - * ScreenOS (Netscreen) + * [JunOS](lib/oxidized/model/junos.rb) + * [ScreenOS (Netscreen)](lib/oxidized/model/screenos.rb) * Mellanox - * MLNX-OS + * [MLNX-OS](lib/oxidized/model/mlnxos.rb) * Mikrotik - * RouterOS + * [RouterOS](lib/oxidized/model/routeros.rb) * Motorola - * RFS + * [RFS](lib/oxidized/model/mtrlrfs.rb) * MRV - * MasterOS + * [MasterOS](lib/oxidized/model/masteros.rb) + * [FiberDriver](lib/oxidized/model/fiberdriver.rb) * Netonix - * WISP Switch (As Netonix) + * [WISP Switch (As Netonix)](lib/oxidized/model/netonix.rb) * Nokia (formerly TiMetra, Alcatel, Alcatel-Lucent) - * SR OS (TiMOS) + * [SR OS (TiMOS)](lib/oxidized/model/timos.rb) * Opengear - * Opengear + * [Opengear](lib/oxidized/model/opengear.rb) * Palo Alto - * PANOS - * pfSense + * [PANOS](lib/oxidized/model/panos.rb) + * [PLANET SG/SGS Switches](lib/oxidized/model/planet.rb) + * [pfSense](lib/oxidized/model/pfsense.rb) * Quanta - * Quanta / VxWorks 6.6 (1.1.0.8) + * [Quanta / VxWorks 6.6 (1.1.0.8)](lib/oxidized/model/quantaos.rb) * Supermicro - * Supermicro + * [Supermicro](lib/oxidized/model/supermicro.rb) + * Trango Systems + * [Trango](lib/oxidized/model/trango.rb) * Ubiquiti - * AirOS - * Edgeos - * EdgeSwitch + * [AirOS](lib/oxidized/model/airos.rb) + * [Edgeos](lib/oxidized/model/edgeos.rb) + * [EdgeSwitch](lib/oxidized/model/edgeswitch.rb) * Watchguard - * Fireware OS + * [Fireware OS](lib/oxidized/model/firewareos.rb) * Zyxel - * ZyNOS + * [ZyNOS](lib/oxidized/model/zynos.rb) # Installation @@ -149,7 +169,7 @@ gem install oxidized-script oxidized-web # if you don't install oxidized-web, ma ``` ## CentOS, Oracle Linux, Red Hat Linux -On CentOS 6 / RHEL 6, install Ruby 1.9.3 or greater (for Ruby 2.1.2 installation instructions see "Installing Ruby 2.1.2 using RVM"), then install Oxidized dependencies +On CentOS 6 / RHEL 6, install Ruby greater than 1.9.3 (for Ruby 2.1.2 installation instructions see "Installing Ruby 2.1.2 using RVM"), then install Oxidized dependencies ```shell yum install cmake sqlite-devel openssl-devel libssh2-devel ``` @@ -256,46 +276,72 @@ rvm use --default 2.1.2 ``` # Running with Docker -1. clone git repo: + +clone git repo: ``` - root@bla:~# git clone https://github.com/ytti/oxidized +git clone https://github.com/ytti/oxidized +``` + +build container locally: + ``` -2. build container locally: +docker build -q -t oxidized/oxidized:latest oxidized/ ``` - root@bla:~# docker build -q -t oxidized/oxidized:latest oxidized/ + +create config directory in main system: + ``` -3. create config directory in main system: +mkdir /etc/oxidized ``` - root@bla~:# mkdir /etc/oxidized + +run container the first time: +_Note: this step in only needed for creating Oxidized's configuration file and can be skipped if you already have it + ``` -4. run container the first time: +docker run --rm -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -t oxidized/oxidized:latest oxidized ``` - root@bla:~# docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -t oxidized/oxidized:latest oxidized +If the RESTful API and Web Interface are enabled, on the docker host running the container +edit /etc/oxidized/config and modify 'rest: 127.0.0.1:8888' by 'rest: 0.0.0.0:8888' +this will bind port 8888 to all interfaces then expose port out. (Issue #445) + +You can also use docker-compose to launch oxidized container: ``` -5. add 'router.db' to /etc/oxidized: +# docker-compose.yml +# docker-compose file example for oxidized that will start along with docker daemon +oxidized: + restart: always + image: oxidized/oxidized:latest + ports: + - 8888:8888/tcp + environment: + CONFIG_RELOAD_INTERVAL: 600 + volumes: + - /etc/oxidized:/root/.config/oxidized ``` - root@bla:~# vim /etc/oxidized/router.db - [ ... ] - root@bla:~# + +create the `/etc/oxidized/router.db` + ``` -6. run container again: +vim /etc/oxidized/router.db ``` - root@bla:~# docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -t oxidized/oxidized:latest - oxidized[1]: Oxidized starting, running as pid 1 - oxidized[1]: Loaded 1 nodes - Puma 2.13.4 starting... - * Min threads: 0, max threads: 16 - * Environment: development - * Listening on tcp://0.0.0.0:8888 - ^C - root@bla:~# +run container again: + +``` +docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -t oxidized/oxidized:latest +oxidized[1]: Oxidized starting, running as pid 1 +oxidized[1]: Loaded 1 nodes +Puma 2.13.4 starting... +* Min threads: 0, max threads: 16 +* Environment: development +* Listening on tcp://0.0.0.0:8888 ``` If you want to have the config automatically reloaded (e.g. when using a http source that changes) + ``` - root@bla:~# docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -e CONFIG_RELOAD_INTERVAL=3600 -t oxidized/oxidized:latest +docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -e CONFIG_RELOAD_INTERVAL=3600 -t oxidized/oxidized:latest ``` ## Cookbook @@ -334,10 +380,10 @@ Device models can contain substitution filters to remove potentially sensitive d As a partial example from ios.rb: -``` +``` cmd :secret do |cfg| cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>' - (...) + (...) cfg end ``` @@ -388,6 +434,31 @@ vars_map: ssh_proxy: 3 ... ``` +### Source: SQL + Oxidized uses the `sequel` ruby gem. You can use a variety of databases that aren't explicitly listed. For more information visit https://github.com/jeremyevans/sequel Make sure you have the correct adapter! +### Source: MYSQL + +```sudo apt-get install libmysqlclient-dev``` + +The values correspond to your fields in the DB such that ip, model, etc are field names in the DB + +``` +source: + default: sql + sql: + adapter: mysql2 + database: oxidized + table: nodes + username: root + password: rootpass + map: + name: ip + model: model + username: username + password: password + vars_map: + enable: enable +``` ### Source: SQLite @@ -435,6 +506,17 @@ source: X-Auth-Token: 'somerandomstring' ``` +You can also pass `secure: false` if you want to disable ssl certificate verification: + +``` +source: + default: http + http: + url: https://url/api + scheme: https + secure: false +``` + ### Output: File Parent directory needs to be created manually, one file per device, with most recent running config. @@ -574,7 +656,7 @@ rest: 10.0.0.1:8000/oxidized ### Advanced Configuration -Below is an advanced example configuration. You will be able to (optinally) override options per device. The router.db format used is ```hostname:model:username:password:enable_password```. Hostname and model will be the only required options, all others override the global configuration sections. +Below is an advanced example configuration. You will be able to (optionally) override options per device. The router.db format used is ```hostname:model:username:password:enable_password```. Hostname and model will be the only required options, all others override the global configuration sections. ``` --- @@ -619,6 +701,28 @@ source: model_map: cisco: ios juniper: junos + +``` + +### Advanced Group Configuration + +For group specific credentials + +``` +groups: + mikrotik: + username: admin + password: blank + ubiquiti: + username: ubnt + password: ubnt +``` +and add group mapping +``` +map: + model: 0 + name: 1 + group: 2 ``` # Hooks @@ -711,6 +815,35 @@ hooks: password: pass ``` +## Hook type: awssns + +The `awssns` hook publishes messages to AWS SNS topics. This allows you to notify other systems of device configuration changes, for example a config orchestration pipeline. Multiple services can subscribe to the same AWS topic. + +Fields sent in the message: + + * `event`: Event type (e.g. `node_success`) + * `group`: Group name + * `model`: Model name (e.g. `eos`) + * `node`: Device hostname + +Configuration example: + +``` yaml +hooks: + hook_script: + type: awssns + events: [node_fail,node_success,post_store] + region: us-east-1 + topic_arn: arn:aws:sns:us-east-1:1234567:oxidized-test-backup_events +``` + +AWS SNS hook requires the following configuration keys: + + * `region`: AWS Region name + * `topic_arn`: ASN Topic reference + +Your AWS credentials should be stored in `~/.aws/credentials`. + # Ruby API The following objects exist in Oxidized. @@ -718,7 +851,7 @@ The following objects exist in Oxidized. ## Input * gets config from nodes * must implement 'connect', 'get', 'cmd' - * 'ssh' and 'telnet' implemented + * 'ssh', 'telnet, ftp, and tftp' implemented ## Output * stores config @@ -19,10 +19,10 @@ task :test do end end -desc 'Install gem' -task :install => :build do - system "sudo -Es sh -c \'umask 022; gem install gems/#{file}\'" -end +## desc 'Install gem' +## task :install => :build do +## system "sudo -Es sh -c \'umask 022; gem install gems/#{file}\'" +## end desc 'Remove gems' task :clean do diff --git a/extra/oxidized.apache2 b/extra/oxidized.apache2 new file mode 100644 index 0000000..0ab372b --- /dev/null +++ b/extra/oxidized.apache2 @@ -0,0 +1,14 @@ +<VirtualHost *:80> + # Place in sites-available + + ServerAdmin admin@example.com + ServerName oxidized.example.com + ServerAlias oxidized + + ProxyPass / http://127.0.0.1:8888/ + ProxyPassReverse / http://127.0.0.1:8888/ + + ErrorLog /var/log/apache2/oxidized_error.log + CustomLog /var/log/apache2/oxidized_access.log combined + +</VirtualHost> diff --git a/extra/oxidized.nginx b/extra/oxidized.nginx new file mode 100644 index 0000000..06a4768 --- /dev/null +++ b/extra/oxidized.nginx @@ -0,0 +1,14 @@ +server { + listen 80; + listen [::]:80; + + server_name oxidized.example.com; + + location / { + proxy_pass http://127.0.0.1:8888/; + } + + access_log /var/log/nginx/access_oxidized.log; + error_log /var/log/nginx/error_oxidized.log; +} + diff --git a/extra/rest_client.rb b/extra/rest_client.rb index a16bd42..35d93ae 100644 --- a/extra/rest_client.rb +++ b/extra/rest_client.rb @@ -2,8 +2,30 @@ module Oxidized class RestClient require 'net/http' require 'json' - HOST = 'localhost' - PORT = 8888 + require 'uri' + require 'asetus' + + class Config + Root = Root = ENV['OXIDIZED_HOME'] || File.join(ENV['HOME'], '.config', 'oxidized') + end + + CFGS = Asetus.new :name=>'oxidized', :load=>false, :key_to_s=>true + CFGS.default.rest = '127.0.0.1:8888' + + begin + CFGS.load + rescue => error + raise InvalidConfig, "Error loading config: #{error.message}" + end + + restcfg = CFGS.cfg.rest + unless restcfg.match(/^http:\/\//) + restcfg.insert(0, 'http://') + end + + HOST = URI(restcfg).host + PORT = URI(restcfg).port + PATH = URI(restcfg).path class << self def next opt={}, host=HOST, port=PORT @@ -18,7 +40,7 @@ module Oxidized def next opt data = JSON.dump opt - @web.put '/node/next/' + opt[:name].to_s, data + @web.put PATH + '/node/next/' + opt[:name].to_s, data end end diff --git a/lib/oxidized/hook/awssns.rb b/lib/oxidized/hook/awssns.rb new file mode 100644 index 0000000..dbc2d47 --- /dev/null +++ b/lib/oxidized/hook/awssns.rb @@ -0,0 +1,27 @@ +require 'aws-sdk' + +class AwsSns < Oxidized::Hook + def validate_cfg! + raise KeyError, 'hook.region is required' unless cfg.has_key?('region') + raise KeyError, 'hook.topic_arn is required' unless cfg.has_key?('topic_arn') + end + + def run_hook(ctx) + sns = Aws::SNS::Resource.new(region: cfg.region) + topic = sns.topic(cfg.topic_arn) + message = { + :event => ctx.event.to_s + } + if ctx.node + message.merge!( + :group => ctx.node.group.to_s, + :model => ctx.node.model.class.name.to_s.downcase, + :node => ctx.node.name.to_s + ) + end + topic.publish({ + message: message.to_json + }) + end + +end diff --git a/lib/oxidized/input/ssh.rb b/lib/oxidized/input/ssh.rb index cd12167..9a5c508 100644 --- a/lib/oxidized/input/ssh.rb +++ b/lib/oxidized/input/ssh.rb @@ -17,8 +17,9 @@ module Oxidized class NoShell < OxidizedError; end def connect node - @node = node - @output = '' + @node = node + @output = '' + @pty_options = { term: "vt100" } @node.model.cfg['ssh'].each { |cb| instance_exec(&cb) } secure = Oxidized.config.input.ssh.secure @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-ssh", 'w') if Oxidized.config.input.debug? @@ -32,9 +33,10 @@ module Oxidized :paranoid => secure, :auth_methods => %w(none publickey password keyboard-interactive), :number_of_password_prompts => 0, - :proxy => proxy + :proxy => proxy, } - ssh_opts[:kex] = vars(:ssh_kex).split(/,\s*/) if vars(:ssh_kex) + ssh_opts[:keys] = vars(:ssh_keys).is_a?(Array) ? vars(:ssh_keys) : [vars(:ssh_keys)] if vars(:ssh_keys) + ssh_opts[:kex] = vars(:ssh_kex).split(/,\s*/) if vars(:ssh_kex) ssh_opts[:encryption] = vars(:ssh_encryption).split(/,\s*/) if vars(:ssh_encryption) Oxidized.logger.debug "lib/oxidized/input/ssh.rb: Connecting to #{@node.name}" @@ -42,7 +44,7 @@ module Oxidized unless @exec shell_open @ssh begin - @username ? shell_login : expect(@node.prompt) + login rescue Timeout::Error raise PromptUndetect, [ @output, 'not matching configured prompt', @node.prompt ].join(' ') end @@ -71,6 +73,10 @@ module Oxidized @output end + def pty_options hash + @pty_options = @pty_options.merge hash + end + private def disconnect @@ -93,7 +99,7 @@ module Oxidized @output << data @output = @node.model.expects @output end - ch.request_pty (_opts={:term=>'vt100'}) do |_ch, success_pty| + ch.request_pty (@pty_options) do |_ch, success_pty| raise NoShell, "Can't get PTY" unless success_pty ch.send_channel_request 'shell' do |_ch, success_shell| raise NoShell, "Can't get shell" unless success_shell @@ -102,13 +108,18 @@ module Oxidized end end - # Cisco WCS has extremely dubious SSH implementation, SSH auth is always - # success, it always opens shell and then run auth in shell. I guess - # they'll never support exec() :) - def shell_login - expect username - cmd @node.auth[:username], password - cmd @node.auth[:password] + # some models have SSH auth or terminal auth based on version of code + # if SSH is configured for terminal auth, we'll still try to detect prompt + def login + if @username + match = expect username, @node.prompt + if match == username + cmd @node.auth[:username], password + cmd @node.auth[:password] + end + else + expect @node.prompt + end end def exec state=nil @@ -123,14 +134,18 @@ module Oxidized @output end - def expect regexp - Oxidized.logger.debug "lib/oxidized/input/ssh.rb: expecting #{regexp.inspect} at #{node.name}" + def expect *regexps + regexps = [regexps].flatten + Oxidized.logger.debug "lib/oxidized/input/ssh.rb: expecting #{regexps.inspect} at #{node.name}" Timeout::timeout(Oxidized.config.timeout) do @ssh.loop(0.1) do sleep 0.1 - not @output.match regexp + match = regexps.find { |regexp| @output.match regexp } + return match if match + true end end end + end end diff --git a/lib/oxidized/input/tftp.rb b/lib/oxidized/input/tftp.rb new file mode 100644 index 0000000..78164d0 --- /dev/null +++ b/lib/oxidized/input/tftp.rb @@ -0,0 +1,41 @@ +module Oxidized + require 'stringio' + require_relative 'cli' + + begin + require 'net/tftp' + rescue LoadError + raise OxidizedError, 'net/tftp not found: sudo gem install net-tftp' + end + + class TFTP < Input + + include Input::CLI + + # TFTP utilizes UDP, there is not a connection. We simply specify an IP and send/receive data. + def connect node + @node = node + + @node.model.cfg['tftp'].each { |cb| instance_exec(&cb) } + @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-tftp", 'w') if Oxidized.config.input.debug? + @tftp = Net::TFTP.new @node.ip + end + + def cmd file + Oxidized.logger.debug "TFTP: #{file} @ #{@node.name}" + config = StringIO.new + @tftp.getbinary file, config + config.rewind + config.read + end + + private + + def disconnect + # TFTP uses UDP, there is no connection to close + ensure + @log.close if Oxidized.config.input.debug? + end + + end +end diff --git a/lib/oxidized/model/acos.rb b/lib/oxidized/model/acos.rb index bb9846e..47649a2 100644 --- a/lib/oxidized/model/acos.rb +++ b/lib/oxidized/model/acos.rb @@ -6,6 +6,13 @@ class ACOS < Oxidized::Model ##ACOS prompt changes depending on the state of the device prompt /^([-\w.\/:?\[\]\(\)]+[#>]\s?)$/ + cmd :secret do |cfg| + cfg.gsub!(/community read encrypted (\S+)/, 'community read encrypted <hidden>') # snmp + cfg.gsub!(/secret encrypted (\S+)/, 'secret encrypted <hidden>') # tacacs-server + cfg.gsub!(/password encrypted (\S+)/, 'password encrypted <hidden>') # user + cfg + end + cmd 'show version' do |cfg| cfg.gsub! /\s(Last configuration saved at).*/, ' \\1 <removed>' cfg.gsub! /\s(Memory).*/, ' \\1 <removed>' @@ -22,11 +29,20 @@ class ACOS < Oxidized::Model comment cfg end + cmd 'show partition-config all' do |cfg| + cfg.gsub! /(Current configuration).*/, '\\1 <removed>' + cfg.gsub! /(Configuration last updated at).*/, '\\1 <removed>' + cfg.gsub! /(Configuration last saved at).*/, '\\1 <removed>' + cfg.gsub! /(Configuration last synchronized at).*/, '\\1 <removed>' + cfg + end + cmd 'show running-config all-partitions' do |cfg| cfg.gsub! /(Current configuration).*/, '\\1 <removed>' cfg.gsub! /(Configuration last updated at).*/, '\\1 <removed>' cfg.gsub! /(Configuration last saved at).*/, '\\1 <removed>' cfg.gsub! /(Configuration last synchronized at).*/, '\\1 <removed>' + cfg end cmd 'show aflex all-partitions' do |cfg| diff --git a/lib/oxidized/model/alvarion.rb b/lib/oxidized/model/alvarion.rb new file mode 100644 index 0000000..3c762de --- /dev/null +++ b/lib/oxidized/model/alvarion.rb @@ -0,0 +1,13 @@ +class Alvarion < Oxidized::Model + + # Used in Alvarion wisp equipment + + # Run this command as an instance of Model so we can access node + pre do + cmd "#{node.auth[:password]}.cfg" + end + + + cfg :tftp {} + +end diff --git a/lib/oxidized/model/aosw.rb b/lib/oxidized/model/aosw.rb index 394561f..11d8442 100644 --- a/lib/oxidized/model/aosw.rb +++ b/lib/oxidized/model/aosw.rb @@ -28,19 +28,21 @@ class AOSW < Oxidized::Model cmd 'show version' do |cfg| cfg = cfg.each_line.select { |line| not line.match /Switch uptime/i } - comment cfg.join + rstrip_cfg comment cfg.join end cmd 'show inventory' do |cfg| - clean cfg + rstrip_cfg clean cfg end cmd 'show slots' do |cfg| - comment cfg + rstrip_cfg comment cfg end + cmd 'show license' do |cfg| - comment cfg + rstrip_cfg comment cfg end + cmd 'show running-config' do |cfg| out = [] cfg.each_line do |line| @@ -60,8 +62,8 @@ class AOSW < Oxidized::Model cfg :telnet, :ssh do if vars :enable post_login do - send 'enable\n' - send vars(:enable) + '\n' + send "enable\n" + cmd vars(:enable) end end post_login 'no paging' @@ -72,6 +74,15 @@ class AOSW < Oxidized::Model pre_logout 'exit' end + def rstrip_cfg cfg + out = [] + cfg.each_line do |line| + out << line.rstrip + end + out = out.join "\n" + out << "\n" + end + def clean cfg out = [] cfg.each_line do |line| diff --git a/lib/oxidized/model/apc_aos.rb b/lib/oxidized/model/apc_aos.rb new file mode 100644 index 0000000..530d436 --- /dev/null +++ b/lib/oxidized/model/apc_aos.rb @@ -0,0 +1,11 @@ +class Apc_aos < Oxidized::Model + + cmd 'config.ini' do |cfg| + cfg.gsub! /^; Configuration file\, generated on.*/, '' + end + + cfg :ftp do + end + +end + diff --git a/lib/oxidized/model/asa.rb b/lib/oxidized/model/asa.rb index a41348e..df30059 100644 --- a/lib/oxidized/model/asa.rb +++ b/lib/oxidized/model/asa.rb @@ -15,7 +15,7 @@ class ASA < Oxidized::Model cfg.gsub! /username (\S+) password (\S+) (.*)/, 'username \1 password <secret hidden> \3' cfg.gsub! /ikev2 pre-shared-key (\S+)/, 'ikev2 pre-shared-key <secret hidden>' cfg.gsub! /ikev2 (remote|local)-authentication pre-shared-key (\S+)/, 'ikev2 \1-authentication pre-shared-key <secret hidden>' - cfg.gsub! /^(aaa-server TACACS\+ \(\S+\) host.*\n\skey) \S+$/m, '\1 <secret hidden>' + cfg.gsub! /^(aaa-server TACACS\+? \(\S+\) host.*\n\skey) \S+$/mi, '\1 <secret hidden>' cfg end diff --git a/lib/oxidized/model/casa.rb b/lib/oxidized/model/casa.rb new file mode 100644 index 0000000..e85c904 --- /dev/null +++ b/lib/oxidized/model/casa.rb @@ -0,0 +1,46 @@ +class Casa < Oxidized::Model + # Casa Systems CMTS + + prompt /^([\w.@()-]+[#>]\s?)$/ + comment '! ' + + cmd :secret do |cfg| + cfg.gsub! /^(snmp community) \S+/, '\\1 <configuration removed>' + cfg.gsub! /^(snmp comm-tbl) \S+ \S+/, '\\1 <removed> <removed>' + cfg.gsub! /^(console-password encrypted) \S+/, '\\1 <secret hidden>' + cfg.gsub! /^(password encrypted) \S+/, '\\1 <secret hidden>' + cfg.gsub! /^(tacacs-server key) \S+/, '\\1 <secret hidden>' + cfg + end + + cmd :all do |cfg| + cfg.each_line.to_a[1..-2].join + end + + cmd 'show system' do |cfg| + comment cfg.each_line.reject { |line| line.match /^\s+System (Time|Uptime): / }.join + end + + cmd 'show version' do |cfg| + comment cfg + end + + cmd 'show run' + + cfg :telnet do + username /^Username:/ + password /^Password:/ + end + + cfg :telnet, :ssh do + post_login 'page-off' + # preferred way to handle additional passwords + if vars :enable + post_login do + send "enable\n" + cmd vars(:enable) + end + end + pre_logout 'logout' + end +end diff --git a/lib/oxidized/model/catos.rb b/lib/oxidized/model/catos.rb index 874ebbc..bac9eec 100644 --- a/lib/oxidized/model/catos.rb +++ b/lib/oxidized/model/catos.rb @@ -1,6 +1,6 @@ class Catos < Oxidized::Model - prompt /^[\w.@-]+> \(enable\) $/ + prompt /^[\w.@-]+>\s?(\(enable\) )?$/ comment '# ' cmd :all do |cfg| @@ -28,8 +28,15 @@ class Catos < Oxidized::Model password /^Password:/ end - cfg :ssh, :telnet do + cfg :telnet, :ssh do post_login 'set length 0' + # preferred way to handle additional passwords + if vars :enable + post_login do + send "enable\n" + cmd vars(:enable) + end + end pre_logout 'exit' end diff --git a/lib/oxidized/model/ciscosmb.rb b/lib/oxidized/model/ciscosmb.rb index 3ef9a85..e5501d5 100644 --- a/lib/oxidized/model/ciscosmb.rb +++ b/lib/oxidized/model/ciscosmb.rb @@ -33,14 +33,13 @@ class CiscoSMB < Oxidized::Model cfg end - cfg :telnet do - username /^User Name:/ - password /^\r?Password:$/ - end - cfg :telnet, :ssh do + username /^User ?[nN]ame:/ + password /^\r?Password:$/ post_login 'terminal datadump' # Disable pager post_login 'terminal width 0' + post_login 'terminal len 0' + pre_logout 'exit' #exit returns to previous priv level, no way to quit from exec(#) pre_logout 'exit' end diff --git a/lib/oxidized/model/dlink.rb b/lib/oxidized/model/dlink.rb new file mode 100644 index 0000000..5756bad --- /dev/null +++ b/lib/oxidized/model/dlink.rb @@ -0,0 +1,36 @@ +class Dlink < Oxidized::Model + # D-LINK Switches + + prompt /^(\r*[\w.@():-]+[#>]\s?)$/ + comment '# ' + + cmd :secret do |cfg| + cfg.gsub! /^(create snmp community) \S+/, '\\1 <removed>' + cfg.gsub! /^(create snmp group) \S+/, '\\1 <removed>' + cfg + end + + cmd :all do |cfg| + cfg.each_line.to_a[2..-2].map{|line|line.delete("\r").rstrip}.join("\n") + "\n" + end + + cmd 'show switch' do |cfg| + comment cfg + end + + cmd 'show vlan' do |cfg| + comment cfg + end + + cmd 'show config current' + + cfg :telnet do + username /\r*username:/ + password /\r*password:/ + end + + cfg :telnet, :ssh do + post_login 'disable clipaging' + pre_logout 'logout' + end +end diff --git a/lib/oxidized/model/dnos.rb b/lib/oxidized/model/dnos.rb index 1c31aad..a44630e 100644 --- a/lib/oxidized/model/dnos.rb +++ b/lib/oxidized/model/dnos.rb @@ -33,15 +33,16 @@ class DNOS < Oxidized::Model end cfg :telnet, :ssh do - post_login 'terminal length 0' - post_login 'terminal width 0' if vars :enable post_login do send "enable\n" - send vars(:enable) + "\n" + cmd vars(:enable) end end + post_login 'terminal length 0' + post_login 'terminal width 0' pre_logout 'exit' + pre_logout 'exit' end end diff --git a/lib/oxidized/model/eos.rb b/lib/oxidized/model/eos.rb index 75da0fa..a9f3ff3 100644 --- a/lib/oxidized/model/eos.rb +++ b/lib/oxidized/model/eos.rb @@ -22,7 +22,7 @@ class EOS < Oxidized::Model comment cfg end - cmd 'show running-config | no-more' do |cfg| + cmd 'show running-config | no-more | exclude ! Time:' do |cfg| cfg end diff --git a/lib/oxidized/model/fiberdriver.rb b/lib/oxidized/model/fiberdriver.rb new file mode 100644 index 0000000..8f8eb07 --- /dev/null +++ b/lib/oxidized/model/fiberdriver.rb @@ -0,0 +1,21 @@ +class FiberDriver < Oxidized::Model + prompt /\w+#/ + comment "! " + + cmd :all do |cfg| + cfg.each_line.to_a[1..-2].join + end + cmd 'show inventory' do |cfg| + comment cfg + end + + cmd "show running-config" do |cfg| + cfg.each_line.to_a[3..-1].join + end + + cfg :ssh do + post_login 'terminal length 0' + post_login 'terminal width 512' + pre_logout 'exit' + end +end diff --git a/lib/oxidized/model/fujitsupy.rb b/lib/oxidized/model/fujitsupy.rb new file mode 100644 index 0000000..20a78dd --- /dev/null +++ b/lib/oxidized/model/fujitsupy.rb @@ -0,0 +1,42 @@ +class FujitsuPY < Oxidized::Model + + prompt /^(\([\w.-]*\)\s#|^\S+\#\s)$/ + comment '! ' + + cmd :all do |cfg| + cfg.each_line.to_a[1..-2].join + end + +# 1Gbe switch + cmd 'show version' do |cfg| + cfg.gsub! /^(<ERROR> : 2 : format error)$/, '' + comment cfg + end + +# 10Gbe switch + cmd 'show system information' do |cfg| + cfg.gsub! /^Current-time : [\w\s:]*$/, '' + cfg.gsub! /^(\s{33}\^)$/, '' + cfg.gsub! /^(\% Invalid input detected at '\^' marker.)$/, '' + comment cfg + end + + cmd 'show running-config' do |cfg| + cfg + end + + cfg :telnet do + username /^Username:/ + password /^Password:/ + end + + cfg :telnet, :ssh do + post_login 'no pager' + post_login 'terminal pager disable' + pre_logout do + send "quit\n" + send "n\n" + end + end + +end diff --git a/lib/oxidized/model/hatteras.rb b/lib/oxidized/model/hatteras.rb new file mode 100644 index 0000000..4192cbc --- /dev/null +++ b/lib/oxidized/model/hatteras.rb @@ -0,0 +1,52 @@ +class Hatteras < Oxidized::Model + # Hatteras Networks + + prompt /^(\r?[\w.@()-]+[#>]\s?)$/ + comment '# ' + + expect /WARNING: System configuration changes will be lost when the device restarts./ do |data, re| + send "y\r" + data.sub re, '' + end + + + cmd :secret do |cfg| + cfg.gsub! /^(community) \S+/, '\\1 "<configuration removed>"' + cfg.gsub! /^(communityString) "\S+"/, '\\1 "<configuration removed>"' + cfg.gsub! /^(key) "\S+"/, '\\1 "<secret hidden>"' + cfg + end + + cmd :all do |cfg| + cfg.each_line.to_a[1..-2].join + end + + cmd "show switch\r" do |cfg| + cfg = cfg.each_line.reject { |line| line.match /Switch uptime|Switch temperature|Last reset reason/ or + line.match /TermCpuUtil|^\s+\^$|ERROR: Bad command/ }.join + comment cfg + end + + cmd "show card\r" do |cfg| + cfg = cfg.each_line.reject { |line| line.match /Card uptime|Card temperature|Last reset reason/ or + line.match /TermCpuUtil|^\s+\^$|ERROR: Bad command/ }.join + comment cfg + end + + cmd "show sfp *\r" do |cfg| + comment cfg + end + + cmd "show config run\r" do |cfg| + cfg + end + + cfg :telnet do + username /^Login:/ + password /^Password:/ + end + + cfg :telnet, :ssh do + pre_logout "logout\r" + end +end diff --git a/lib/oxidized/model/hpebladesystem.rb b/lib/oxidized/model/hpebladesystem.rb new file mode 100644 index 0000000..5e34de8 --- /dev/null +++ b/lib/oxidized/model/hpebladesystem.rb @@ -0,0 +1,83 @@ +class HPEBladeSystem < Oxidized::Model + # HPE Onboard Administrator + + prompt /.*> / + comment '# ' + + expect /^\s*--More--\s+.*$/ do |data, re| + send ' ' + data.sub re, '' + end + + cmd :all do |cfg| + cfg = cfg.delete("\r").each_line.to_a[0..-1].map{|line|line.rstrip}.join("\n") + "\n" + cfg.each_line.to_a[0..-2].join + end + + cmd :secret do |cfg| + cfg.gsub! /^(SET SNMP COMMUNITY (READ|WRITE)).*/, '\\1 <configuration removed>' + cfg + end + + cmd 'show oa info' do |cfg| + comment cfg + end + + cmd 'show oa network' do |cfg| + comment cfg + end + + cmd 'show oa certificate' do |cfg| + comment cfg + end + + cmd 'show sshfingerprint' do |cfg| + comment cfg + end + + cmd 'show fru' do |cfg| + comment cfg + end + + cmd 'show network' do |cfg| + comment cfg + end + + cmd 'show vlan' do |cfg| + comment cfg + end + + cmd 'show rack name' do |cfg| + comment cfg + end + + cmd 'show server list' do |cfg| + comment cfg + end + + cmd 'show server names' do |cfg| + comment cfg + end + + cmd 'show server port map all' do |cfg| + comment cfg + end + + cmd 'show server info all' do |cfg| + comment cfg + end + + cmd 'show config' do |cfg| + cfg.gsub! /^#(Generated on:) .*$/, '\\1 <removed>' + cfg.gsub /^\s+/, '' + end + + cfg :telnet do + username /\slogin:/ + password /^Password: / + end + + cfg :telnet, :ssh do + pre_logout "exit" + end +end diff --git a/lib/oxidized/model/ios.rb b/lib/oxidized/model/ios.rb index 1f099c8..3cbe0f0 100644 --- a/lib/oxidized/model/ios.rb +++ b/lib/oxidized/model/ios.rb @@ -26,7 +26,9 @@ class IOS < Oxidized::Model cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>' cfg.gsub! /username (\S+) privilege (\d+) (\S+).*/, '<secret hidden>' cfg.gsub! /^username \S+ password \d \S+/, '<secret hidden>' + cfg.gsub! /^username \S+ secret \d \S+/, '<secret hidden>' cfg.gsub! /^enable password \d \S+/, '<secret hidden>' + cfg.gsub! /^enable secret \d \S+/, '<secret hidden>' cfg.gsub! /wpa-psk ascii \d \S+/, '<secret hidden>' cfg.gsub! /^tacacs-server key \d \S+/, '<secret hidden>' cfg diff --git a/lib/oxidized/model/ironware.rb b/lib/oxidized/model/ironware.rb index 1e8c30e..db341d1 100644 --- a/lib/oxidized/model/ironware.rb +++ b/lib/oxidized/model/ironware.rb @@ -2,14 +2,14 @@ class IronWare < Oxidized::Model prompt /^.*(telnet|ssh)\@.+[>#]\s?$/i comment '! ' - + #to handle pager without enable #expect /^((.*)--More--(.*))$/ do |data, re| # send ' ' # data.sub re, '' #end - + #to remove backspace (if handle pager without enable) #expect /^((.*)[\b](.*))$/ do |data, re| # data.sub re, '' @@ -44,14 +44,14 @@ class IronWare < Oxidized::Model out << sc.rest cfg = out end - + comment cfg end - + cmd 'show flash' do |cfg| comment cfg end - + cmd 'show module' do |cfg| cfg.gsub! /^((Invalid input)|(Type \?)).*$/, '' # some ironware devices are fixed config comment cfg @@ -74,7 +74,7 @@ class IronWare < Oxidized::Model if vars :enable post_login do send "enable\r\n" - send vars(:enable) + "\r\n" + cmd vars(:enable) end end post_login '' diff --git a/lib/oxidized/model/pfsense.rb b/lib/oxidized/model/pfsense.rb index cd6885c..c02c0d0 100644 --- a/lib/oxidized/model/pfsense.rb +++ b/lib/oxidized/model/pfsense.rb @@ -1,20 +1,14 @@ class PfSense < Oxidized::Model - - comment '# ' - - #add a comment in the final conf - def add_comment comment - "\n###### #{comment} ######\n" - end + # use other use than 'admin' user, 'admin' user cannot get ssh/exec. See issue #535 + cmd :all do |cfg| cfg.each_line.to_a[1..-2].join end - #show the persistent configuration - pre do - cfg = add_comment 'Configuration' - cfg += cmd 'cat /cf/conf/config.xml' + cmd 'cat /cf/conf/config.xml' do |cfg| + cfg.gsub! /\s<revision>\s*.*\s*<time>\d*<\/time>\s*.*\s*<\/revision>/, '' + cfg end cfg :ssh do diff --git a/lib/oxidized/model/planet.rb b/lib/oxidized/model/planet.rb new file mode 100644 index 0000000..05a369a --- /dev/null +++ b/lib/oxidized/model/planet.rb @@ -0,0 +1,83 @@ +class Planet < Oxidized::Model + + prompt /^\r?([\w.@()-]+[#>]\s?)$/ + comment '! ' + + # example how to handle pager + #expect /^\s--More--\s+.*$/ do |data, re| + # send ' ' + # data.sub re, '' + #end + + # non-preferred way to handle additional PW prompt + #expect /^[\w.]+>$/ do |data| + # send "enable\n" + # send vars(:enable) + "\n" + # data + #end + + cmd :all do |cfg| + #cfg.gsub! /\cH+\s{8}/, '' # example how to handle pager + #cfg.gsub! /\cH+/, '' # example how to handle pager + cfg.each_line.to_a[1..-2].join + end + + cmd :secret do |cfg| + cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>' + cfg.gsub! /username (\S+) privilege (\d+) (\S+).*/, '<secret hidden>' + cfg.gsub! /^username \S+ password \d \S+/, '<secret hidden>' + cfg.gsub! /^enable password \d \S+/, '<secret hidden>' + cfg.gsub! /wpa-psk ascii \d \S+/, '<secret hidden>' + cfg.gsub! /^tacacs-server key \d \S+/, '<secret hidden>' + cfg + end + + cmd 'show version' do |cfg| + cfg.gsub! "\n\r", "\n" + @planetgs = true if cfg.match /^System Name\w*:\w*GS-.*$/ + @planetsgs = true if cfg.match /SGS-(.*) Device, Compiled on .*$/ + + cfg = cfg.each_line.to_a[0...-2] + + # Strip system time and system uptime from planet gs switches + cfg = cfg.reject { |line| line.match /System Time\s*:.*/ } + cfg = cfg.reject { |line| line.match /System Uptime\s*:.*/ } + + comment cfg.join + end + + + cmd 'show running-config' do |cfg| + cfg.gsub! "\n\r", "\n" + cfg = cfg.each_line.to_a + + cfg = cfg.reject { |line| line.match "Building configuration..." } + + if @planetsgs + cfg << cmd('show transceiver detail | include transceiver detail information|found|Type|length|Nominal|wavelength|Base information') do |cfg| + comment cfg + end + end + + cfg.join + end + + + cfg :telnet do + username /^Username:/ + password /^Password:/ + end + + cfg :telnet, :ssh do + post_login 'terminal length 0' + # preferred way to handle additional passwords + if vars :enable + post_login do + send "enable\n" + cmd vars(:enable) + end + end + pre_logout 'exit' + end + +end diff --git a/lib/oxidized/model/powerconnect.rb b/lib/oxidized/model/powerconnect.rb index f0fa3df..ac36c26 100644 --- a/lib/oxidized/model/powerconnect.rb +++ b/lib/oxidized/model/powerconnect.rb @@ -39,7 +39,7 @@ class PowerConnect < Oxidized::Model if vars :enable post_login do send "enable\n" - send vars(:enable) + "\n" + cmd vars(:enable) end end diff --git a/lib/oxidized/model/procurve.rb b/lib/oxidized/model/procurve.rb index da792e6..c117df3 100644 --- a/lib/oxidized/model/procurve.rb +++ b/lib/oxidized/model/procurve.rb @@ -57,4 +57,8 @@ class Procurve < Oxidized::Model pre_logout "logout\ny\nn" end + cfg :ssh do + pty_options({ chars_wide: 1000 }) + end + end diff --git a/lib/oxidized/model/routeros.rb b/lib/oxidized/model/routeros.rb index 4822500..a92ad5e 100644 --- a/lib/oxidized/model/routeros.rb +++ b/lib/oxidized/model/routeros.rb @@ -1,5 +1,5 @@ class RouterOS < Oxidized::Model - prompt /\[\w+@\S+\]\s?>\s?$/ + prompt /\[\w+@\S+(\s?\S+)*\]\s?>\s?$/ comment "# " cmd '/system routerboard print' do |cfg| @@ -8,6 +8,7 @@ class RouterOS < Oxidized::Model cmd '/export' do |cfg| cfg.gsub! /\x1B\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[m|K]/, '' # strip ANSI colours + cfg.gsub! /\\\r\n\s+/, '' # strip new line cfg = cfg.split("\n").select { |line| not line[/^\#\s\w{3}\/\d{2}\/\d{4}.*$/] } cfg.join("\n") + "\n" end diff --git a/lib/oxidized/model/trango.rb b/lib/oxidized/model/trango.rb new file mode 100644 index 0000000..b2aa1e7 --- /dev/null +++ b/lib/oxidized/model/trango.rb @@ -0,0 +1,62 @@ +class Trango < Oxidized::Model + # take a Trangolink sysinfo output and turn it into a configuration file + + prompt /^#>\s?/ + comment '# ' + + cmd 'sysinfo' do |cfg| + out = [] + comments = [] + cfg.each_line do |line| + if line.match /\[Opmode\] (off|on) \[Default Opmode\] (off|on)/ + out << "opmode " + Regexp.last_match[1] + out << "defaultopmode " + Regexp.last_match[2] + end + if line.match /\[Tx Power\] ([\-\d]+) dBm/ + out << "power " + Regexp.last_match[1] + end + if line.match /\[Active Channel\] (\d+) (v|h)/ + out << "freq " + Regexp.last_match[1] + ' ' + Regexp.last_match[2] + end + if line.match /\[Peer ID\] ([A-F0-9]+)/ + out << "peerid " + Regexp.last_match[1] + end + if line.match /\[Unit Type\] (\S+)/ + out << "utype " + Regexp.last_match[1] + end + if line.match /\[(Hardware Version|Firmware Version|Model|S\/N)\] (\S+)/ + comments << '# ' + Regexp.last_match[1] + ': ' + Regexp.last_match[2] + end + if line.match /\[Remarks\] (\S+)/ + out << "remarks " + Regexp.last_match[1] + end + if line.match /\[RSSI LED\] (on|off)/ + out << "rssiled " + Regexp.last_match[1] + end + if line.match /\[Speed\] (\d+) Mbps/ + speed = Regexp.last_match[1] + end + if line.match /\[Tx MIR\] (\d+) Kbps/ + out << "mir ".concat(Regexp.last_match[1]) + end + if line.match /\[Auto Rate Shift\] (on|off)/ + out << "autorateshift ".concat(Regexp.last_match[1]) + if Regexp.last_match[1].eql? 'off' + out << "speed $speed" + end + end + if line.match /\[IP\] (\S+) \[Subnet Mask\] (\S+) \[Gateway\] (\S+)/ + out << "ipconfig " + Regexp.last_match[1] + ' ' + + Regexp.last_match[2] + ' ' + + Regexp.last_match[3] + end + end + comments.push(*out).join "\n" + end + + cfg :telnet do + password /Password:/ + pre_logout 'exit' + end + +end diff --git a/lib/oxidized/node.rb b/lib/oxidized/node.rb index f2b125a..b13ce0e 100644 --- a/lib/oxidized/node.rb +++ b/lib/oxidized/node.rb @@ -128,24 +128,15 @@ module Oxidized end def resolve_auth opt - # Resolve configured username/password, give priority to group level configuration - # TODO: refactor to use revised behaviour of Asetus - cfg_username, cfg_password = - if Oxidized.config.groups.has_key?(@group) and ['username', 'password'].all? {|e| Oxidized.config.groups[@group].has_key?(e)} - [Oxidized.config.groups[@group].username, Oxidized.config.groups[@group].password] - elsif ['username', 'password'].all? {|e| Oxidized.config.has_key?(e)} - [Oxidized.config.username, Oxidized.config.password] - else - [nil, nil] - end - auth = {} - auth[:username] = (opt[:username] or cfg_username) - auth[:password] = (opt[:password] or cfg_password) - auth + # Resolve configured username/password + { + username: resolve_key(:username, opt), + password: resolve_key(:password, opt), + } end def resolve_input opt - inputs = (opt[:input] or Oxidized.config.input.default) + inputs = resolve_key :input, opt, Oxidized.config.input.default inputs.split(/\s*,\s*/).map do |input| if not Oxidized.mgr.input[input] Oxidized.mgr.add_input input or raise MethodNotFound, "#{input} not found for node #{ip}" @@ -155,7 +146,7 @@ module Oxidized end def resolve_output opt - output = (opt[:output] or Oxidized.config.output.default) + output = resolve_key :output, opt, Oxidized.config.output.default if not Oxidized.mgr.output[output] Oxidized.mgr.add_output output or raise MethodNotFound, "#{output} not found for node #{ip}" end @@ -163,7 +154,7 @@ module Oxidized end def resolve_model opt - model = (opt[:model] or Oxidized.config.model) + model = resolve_key :model, opt if not Oxidized.mgr.model[model] Oxidized.logger.debug "lib/oxidized/node.rb: Loading model #{model.inspect}" Oxidized.mgr.add_model model or raise ModelNotFound, "#{model} not found for node #{ip}" @@ -187,6 +178,33 @@ module Oxidized end end + def resolve_key key, opt, global=nil + # resolve key, first get global, then get group then get node config + key_sym = key.to_sym + key_str = key.to_s + value = global + Oxidized.logger.debug "node.rb: resolving node key '#{key}', with passed global value of '#{value}' and node value '#{opt[key_sym]}'" + + #global + if not value and Oxidized.config.has_key?(key_str) + value = Oxidized.config[key_str] + Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from global" + end + + #group + if Oxidized.config.groups.has_key?(@group) + if Oxidized.config.groups[@group].has_key?(key_str) + value = Oxidized.config.groups[@group][key_str] + Oxidized.logger.debug "node.rb: setting node key '#{key}' to value '#{value}' from group" + end + end + + #node + value = opt[key_sym] || value + Oxidized.logger.debug "node.rb: returning node key '#{key}' with value '#{value}'" + value + end + def is_git? opt (opt[:output] || Oxidized.config.output.default) == 'git' end diff --git a/lib/oxidized/output/file.rb b/lib/oxidized/output/file.rb index bb13827..45f72e1 100644 --- a/lib/oxidized/output/file.rb +++ b/lib/oxidized/output/file.rb @@ -17,7 +17,7 @@ class OxidizedFile < Output end def store node, outputs, opt={} - file = @cfg.directory + file = File.expand_path @cfg.directory if opt[:group] file = File.join File.dirname(file), opt[:group] end @@ -28,18 +28,22 @@ class OxidizedFile < Output end def fetch node, group - cfg_dir = @cfg.directory + cfg_dir = File.expand_path @cfg.directory + node_name = node.name + if group # group is explicitly defined by user - IO.readlines File.join(cfg_dir, group, node) + cfg_dir = File.join File.dirname(cfg_dir), group + File.read File.join(cfg_dir, node_name) else - if File.exists? File.join(cfg_dir, node) # node configuration file is stored on base directory - IO.readlines File.join(cfg_dir, node) + if File.exists? File.join(cfg_dir, node_name) # node configuration file is stored on base directory + File.read File.join(cfg_dir, node_name) else - path = Dir.glob File.join(cfg_dir, '**', node) # fetch node in all groups - return nil if path[0].nil? - open(path[0], 'r').readlines + path = Dir.glob(File.join(File.dirname(cfg_dir), '**', node_name)).first # fetch node in all groups + File.read path end end + rescue Errno::ENOENT + return nil end def version node, group diff --git a/lib/oxidized/source/csv.rb b/lib/oxidized/source/csv.rb index a0ce848..d498e0b 100644 --- a/lib/oxidized/source/csv.rb +++ b/lib/oxidized/source/csv.rb @@ -20,18 +20,20 @@ class CSV < Source nodes = [] open(File.expand_path @cfg.file).each_line do |line| next if line.match(/^\s*#/) - data = line.chomp.split @cfg.delimiter + data = line.chomp.split(@cfg.delimiter, -1) next if data.empty? # map node parameters keys = {} @cfg.map.each do |key, position| - keys[key.to_sym] = data[position] + keys[key.to_sym] = node_var_interpolate data[position] end keys[:model] = map_model keys[:model] if keys.key? :model - # map node specific vars, empty value is considered as nil + # map node specific vars vars = {} - @cfg.vars_map.each { |key, position| vars[key.to_sym] = data[position].to_s.empty? ? nil : data[position] } + @cfg.vars_map.each do |key, position| + vars[key.to_sym] = node_var_interpolate data[position] + end keys[:vars] = vars unless vars.empty? nodes << keys diff --git a/lib/oxidized/source/http.rb b/lib/oxidized/source/http.rb index 93361a2..4fd388b 100644 --- a/lib/oxidized/source/http.rb +++ b/lib/oxidized/source/http.rb @@ -20,6 +20,7 @@ class HTTP < Source uri = URI.parse(@cfg.url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if uri.scheme == 'https' + http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure # map headers headers = {} @@ -39,13 +40,15 @@ class HTTP < Source # map node parameters keys = {} @cfg.map.each do |key, position| - keys[key.to_sym] = line[position] + keys[key.to_sym] = node_var_interpolate line[position] end keys[:model] = map_model keys[:model] if keys.key? :model - # map node specific vars, empty value is considered as nil + # map node specific vars vars = {} - @cfg.vars_map.each { |key, position| vars[key.to_sym] = line[position].to_s.empty? ? nil : line[position] } + @cfg.vars_map.each do |key, position| + vars[key.to_sym] = node_var_interpolate line[position] + end keys[:vars] = vars unless vars.empty? nodes << keys diff --git a/lib/oxidized/source/source.rb b/lib/oxidized/source/source.rb index 7862dd1..9b8bc94 100644 --- a/lib/oxidized/source/source.rb +++ b/lib/oxidized/source/source.rb @@ -1,11 +1,23 @@ module Oxidized class Source class NoConfig < OxidizedError; end + def initialize @map = (Oxidized.config.model_map or {}) end + def map_model model @map.has_key?(model) ? @map[model] : model end + + def node_var_interpolate var + case var + when "nil" then nil + when "false" then false + when "true" then true + else var + end + end + end end diff --git a/lib/oxidized/source/sql.rb b/lib/oxidized/source/sql.rb index fc1caa8..13fc39b 100644 --- a/lib/oxidized/source/sql.rb +++ b/lib/oxidized/source/sql.rb @@ -26,12 +26,14 @@ class SQL < Source query.each do |node| # map node parameters keys = {} - @cfg.map.each { |key, sql_column| keys[key.to_sym] = node[sql_column.to_sym] } + @cfg.map.each { |key, sql_column| keys[key.to_sym] = node_var_interpolate node[sql_column.to_sym] } keys[:model] = map_model keys[:model] if keys.key? :model # map node specific vars vars = {} - @cfg.vars_map.each { |key, sql_column| vars[key.to_sym] = node[sql_column.to_sym] } + @cfg.vars_map.each do |key, sql_column| + vars[key.to_sym] = node_var_interpolate node[sql_column.to_sym] + end keys[:vars] = vars unless vars.empty? nodes << keys diff --git a/lib/oxidized/version.rb b/lib/oxidized/version.rb index 54defae..073aae9 100644 --- a/lib/oxidized/version.rb +++ b/lib/oxidized/version.rb @@ -1,3 +1,3 @@ module Oxidized - VERSION = '0.16.3' + VERSION = '0.19.0' end diff --git a/oxidized.gemspec b/oxidized.gemspec index bb9366d..c12dcb5 100644 --- a/oxidized.gemspec +++ b/oxidized.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.0.0' s.add_runtime_dependency 'asetus', '~> 0.1' s.add_runtime_dependency 'slop', '~> 3.5' - s.add_runtime_dependency 'net-ssh', '>= 3.0.0', '<3.1' + s.add_runtime_dependency 'net-ssh', '~> 3.0.2' s.add_runtime_dependency 'rugged', '~> 0.21', '>= 0.21.4' if defined?(RUBY_VERSION) && RUBY_VERSION > '2.3' |