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 [](https://travis-ci.org/Shopify/oxidized) - -[](http://badge.fury.io/rb/oxidized) +# Oxidized [](https://travis-ci.org/Shopify/oxidized) [](http://badge.fury.io/rb/oxidized) [](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' | 
