#!/usr/bin/env ruby # IOS: # logging discriminator CFG mnemonics includes CONFIG_I # logging host SERVER discriminator CFG # JunOS: # set system syslog host SERVER interactive-commands notice # set system syslog host SERVER match "^mgd\[[0-9]+\]: UI_COMMIT: .*" # Ports < 1024 need extra privileges, use a port higher than this by setting the port option in your oxidized config file. # To use the default port for syslog (514) you shouldn't pass an argument, but you will need to allow this with: # sudo setcap 'cap_net_bind_service=+ep' /usr/bin/ruby # Config options are: # syslogd # port (Default = 514) # file (Default = messages) # resolve (Default = true) # To stop the resolution of IP's to PTR you can set resolve to false # exit if fork ## TODO: proper daemonize require 'socket' require 'resolv' require_relative 'rest_client' module Oxidized require 'asetus' class Config Root = File.join Dir.home, '.config', 'oxidized' end CFGS = Asetus.new name: 'oxidized', load: false, key_to_s: true CFGS.default.syslogd.port = 514 CFGS.default.syslogd.file = 'messages' CFGS.default.syslogd.resolve = true CFGS.default.syslogd.dns_map = { '(.*)\.strip\.this\.domain\.com' => '\\1', '(.*)\.also\.this\.net' => '\\1' } begin CFGS.load rescue StandardError => e raise InvalidConfig, "Error loading config: #{e.message}" ensure CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password end class SyslogMonitor MSG = { ios: /%SYS-(SW[0-9]+-)?5-CONFIG_I:/, junos: 'UI_COMMIT:', eos: /%SYS-5-CONFIG_I:/, nxos: /%VSHD-5-VSHD_SYSLOG_CONFIG_I:/, aruba: 'Notice-Type=\'Running' }.freeze class << self def udp(port = Oxidized::CFG.syslogd.port, listen = 0) io = UDPSocket.new io.bind listen, port new io, :udp end def file(syslog_file = Oxidized::CFG.syslogd.file) io = File.open syslog_file, 'r' io.seek 0, IO::SEEK_END new io, :file end end private def initialize(io, mode = :udp) @mode = mode run io end def rest(opt) Oxidized::RestClient.next opt end def ios(log, index, **opts) # TODO: we need to fetch 'ip/name' in mode == :file here opts[:user] = log[index + 5] opts[:from] = log[-1][1..-2] opts end alias nxos ios alias eos ios def junos(log, index, **opts) # TODO: we need to fetch 'ip/name' in mode == :file here opts[:user] = log[index + 2][1..-2] opts[:msg] = log[(index + 6)..-1].join(' ')[10..-2] opts.delete(:msg) if opts[:msg] == 'none' opts end def aruba(log, index, **opts) opts.merge user: log[index + 2].split('=')[4].split(',')[0][1..-2] end def handle_log(log, ipaddr) log = log.to_s.split index, vendor = MSG.find do |key, value| index = log.find_index { |e| e.match value } break index, key if index end rest send(vendor, log, index, ip: ipaddr, name: getname(ipaddr), model: vendor.to_s) if index end def run(io) loop do log = select [io] log, ip = log.first.first, nil if @mode == :udp log, ip = log.recvfrom_nonblock 2000 ip = ip.last else begin log = log.read_nonblock 2000 rescue EOFError sleep 1 retry end end handle_log log, ip end end def getname(ipaddr) if Oxidized::CFG.syslogd.resolve == false ipaddr else name = (Resolv.getname ipaddr.to_s rescue ipaddr) Oxidized::CFG.syslogd.dns_map.each { |re, sub| name.sub! Regexp.new(re.to_s), sub } name end end end end Oxidized::SyslogMonitor.udp # Oxidized::SyslogMonitor.file '/var/log/poop'