hackedteam/rcs-collector

View on GitHub
lib/rcs-collector/config.rb

Summary

Maintainability
C
1 day
Test Coverage
#
#  Configuration parsing module
#

# from RCS::Common
require 'rcs-common/trace'

# system
require 'yaml'
require 'pp'
require 'optparse'
require 'net/http'

module RCS
module Collector

class Config
  include Singleton
  include Tracer

  CONF_DIR = 'config'
  CONF_FILE = 'config.yaml'

  DEFAULT_CONFIG= {'DB_ADDRESS' => 'rcs-server',
                   'DB_PORT' => 443,
                   'DB_CERT' => 'rcs.pem',
                   'DB_SIGN' => 'rcs-server.sig',
                   'LISTENING_PORT' => 80,
                   'HB_INTERVAL' => 30,
                   'NC_INTERVAL' => 30,
                   'CONTROLLER_PORT' => 4499,
                   'RESOLVE_IP' => true,
                   'SSL_VERIFY' => true}

  attr_reader :global

  def initialize
    @global = {}
  end

  def load_from_file
    trace :info, "Loading configuration file..."
    conf_file = File.join Dir.pwd, CONF_DIR, CONF_FILE

    # load the config in the @global hash
    begin
      File.open(conf_file, "r") do |f|
        @global = YAML.load(f.read)
      end
    rescue
      trace :fatal, "Cannot open config file [#{conf_file}]"
      return false
    end

    unless @global['DB_CERT'].nil? then
      unless File.exist?(Config.instance.file('DB_CERT'))
        trace :fatal, "Cannot open certificate file [#{@global['DB_CERT']}]"
        return false
      end
    end

    unless @global['DB_SIGN'].nil? then
      unless File.exist?(Config.instance.file('DB_SIGN'))
        trace :fatal, "Cannot open signature file [#{@global['DB_SIGN']}]"
        return false
      end
    end

    # to avoid problems with checks too frequent
    if (@global['HB_INTERVAL'] and @global['HB_INTERVAL'] < 10) or (@global['NC_INTERVAL'] and @global['NC_INTERVAL'] < 10)
      trace :fatal, "Interval too short, please increase it"
      return false
    end

    @global['SSL_VERIFY'] = true if @global['SSL_VERIFY'].nil?

    @global['CONTROLLER_PORT'] ||= DEFAULT_CONFIG['CONTROLLER_PORT']

    return true
  end

  def file(name)
    return File.join Dir.pwd, CONF_DIR, @global[name].nil? ? name : @global[name]
  end

  def safe_to_file
    conf_file = File.join Dir.pwd, CONF_DIR, CONF_FILE

    # Write the @global into a yaml file
    begin
      File.open(conf_file, "w") do |f|
        f.write(@global.to_yaml)
      end
    rescue
      trace :fatal, "Cannot write config file [#{conf_file}]"
      return false
    end

    return true
  end

  def run(options)
    if options[:alt_log]
      logfilepath = File.expand_path("../../../log/rcs-collector-config.log", __FILE__)

      @logger = Log4r::Logger.new("migration").tap do |logger|
        logger.outputters << Log4r::Outputter.stdout
        puts "Logging into #{logfilepath}"
        logger.outputters << Log4r::FileOutputter.new('rcs-collector-config', filename: logfilepath)
      end

      __send__(:define_singleton_method, :trace) { |level, message| @logger.__send__(level.to_sym, message) }
    end

    # migration
    if options[:migrate]
      require_relative 'migration'
      Migration.run(logger: @logger)
      return 0
    end

    # load the current config
    load_from_file

    trace :info, ""
    trace :info, "Previous configuration:"
    pp @global

    # use the default values
    if options[:defaults] then
      @global = DEFAULT_CONFIG
    end

    # values taken from command line
    @global['DB_ADDRESS'] = options[:db_address] unless options[:db_address].nil?
    @global['DB_PORT'] = options[:db_port] unless options[:db_port].nil?
    @global['LISTENING_PORT'] = options[:port] unless options[:port].nil?
    @global['HB_INTERVAL'] = options[:hb_interval] unless options[:hb_interval].nil?
    @global['NC_INTERVAL'] = options[:nc_interval] unless options[:nc_interval].nil?

    if options[:auth]
      auth_to_server(options[:user], options[:pass]) do
        puts "User #{options[:user]} authenticated successfully"
      end
      return 0
    end

    if options[:db_sign]
      sig = get_from_server(options[:user], options[:pass], 'server', options)
      File.open(Config.instance.file('DB_SIGN'), 'wb') {|f| f.write sig}
      sig = get_from_server(options[:user], options[:pass], 'network', options)
      File.open(Config.instance.file('rcs-network.sig'), 'wb') {|f| f.write sig}
      sig = get_from_server(options[:user], options[:pass], 'updater', options)
      File.open(Config.instance.file('rcs-updater.sig'), 'wb') {|f| f.write sig}
    end

    if options[:db_cert]
      sig = get_from_server(options[:user], options[:pass], 'server.pem', options)
      File.open(Config.instance.file('DB_CERT'), 'wb') {|f| f.write sig} unless sig.nil?
    end

    trace :info, ""
    trace :info, "Current configuration:"
    pp @global

    # save the configuration
    safe_to_file

    return 0
  end

  def auth_to_server(user, pass)
    http = Net::HTTP.new(@global['DB_ADDRESS'], @global['DB_PORT'])
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    http.open_timeout = 120
    http.read_timeout = 60

    # login
    resp = http.request_post('/auth/login', {:user => user, :pass => pass}.to_json, nil)

    if cookie = resp['Set-Cookie']
      yield(http, cookie) if block_given?

      # logout
      http.request_post('/auth/logout', nil, {'Cookie' => cookie})
    else
      raise("Authentication failed: #{resp.body}")
    end
  end

  def get_from_server(user, pass, resource, options = {})
    trace(:info, "Retrieving #{resource} from the server...")

    begin
      auth_to_server(user, pass) do |http, cookie|
        # get the signature or the cert
        res = http.request_get("/signature/#{resource}", {'Cookie' => cookie})
        sig = JSON.parse(res.body)
        return sig['value']
      end
      trace(:info, "Resource fetched")
    rescue Exception => e
      trace(:fatal, "Unable to fetch #{resource} from server")
      raise(e)
    end
  end

  # executed from rcs-collector-config
  def self.run!(*argv)
    # reopen the class and declare any empty trace method
    # if called from command line, we don't have the trace facility
    self.class_eval do
      def trace(level, message)
        puts message
      end
    end

    # This hash will hold all of the options parsed from the command-line by OptionParser.
    options = {}

    optparse = OptionParser.new do |opts|
      # Set a banner, displayed at the top of the help screen.
      opts.banner = "Usage: rcs-collector-config [options]"

      opts.separator ""
      opts.separator "Database host:"
      opts.on( '-d', '--db-address HOSTNAME', 'Use the rcs-db at HOSTNAME' ) do |host|
        options[:db_address] = host
      end
      opts.on( '-P', '--db-port PORT', Integer, 'Connect to tcp/PORT on rcs-db' ) do |port|
        options[:db_port] = port
      end

      opts.separator ""
      opts.separator "Collector options:"
      opts.on( '-l', '--listen PORT', Integer, 'Listen on tcp/PORT' ) do |port|
        options[:port] = port
      end
      opts.on( '-b', '--db-heartbeat SEC', Integer, 'Time in seconds between two heartbeats to the rcs-db' ) do |sec|
        options[:hb_interval] = sec
      end
      opts.on( '-H', '--nc-heartbeat SEC', Integer, 'Time in seconds between two heartbeats to the network components' ) do |sec|
        options[:nc_interval] = sec
      end

      opts.separator ""
      opts.separator "General options:"
      opts.on( '-X', '--defaults', 'Write a new config file with default values' ) do
        options[:defaults] = true
      end
      opts.on( '-h', '--help', 'Display this screen' ) do
        puts opts
        return 0
      end

      opts.separator ""
      opts.separator "Utilities:"
      opts.on( '-t', '--db-cert', 'Retrieve the certificate file (pem) used from rcs-db (requires --user-pass)' ) do
        options[:db_cert] = true
      end
      opts.on( '-s', '--db-sign', 'Retrieve the signature file (sig) from rcs-db (requires --user-pass)' ) do
        options[:db_sign] = true
      end
      opts.on( '-u', '--user USERNAME', 'rcs-db username' ) do |user|
        options[:user] = user
      end
      opts.on( '-p', '--password PASSWORD', 'rcs-db password' ) do |password|
        options[:pass] = password
      end
      opts.on( '--alternative-log', 'Log output into log/rcs-collector-config' ) do |value|
        options[:alt_log] = true
      end
      opts.on( '--migrate', 'Run the migration script' ) do |value|
        options[:migrate] = true
      end
      opts.on( '--auth', 'Attempt a login to rcs-db with the given user and pass' ) do |value|
        options[:auth] = true
      end
    end

    optparse.parse(argv)

    if options[:db_sign]
      if options[:user].nil? or options[:pass].nil?
        puts "ERROR: You must specify --user-pass"
        return 1
      end
    end

    # execute the configurator
    return Config.instance.run(options)
  end

end #Config
end #Collector::
end #RCS::