require 'soar_auditing_provider_api' require 'soar_auditing_format' require 'soar_configured_factory' require 'time' require 'securerandom' module SoarAuditingProvider class AuditingProvider < SoarAuditingProviderAPI::AuditingProviderAPI private #Aliases for bypassing overridden methods when accessing underlying super class api alias :super_debug :debug alias :super_info :info alias :super_warn :warn alias :super_error :error alias :super_fatal :fatal public attr_accessor :startup_flow_id attr_accessor :service_identifier attr_reader :configuration def initialize(configuration) validate_provider_configuration(configuration) @configuration = configuration super(create_auditors(configuration)) select_auditor(configuration['default_nfrs']) create_auditing_worker @buffer_overflow_count = 0 install_at_exit_handler initialize_metrics end def select_auditor(nfrs) select(nfrs) set_audit_level(@configuration['level'].to_sym) end def set_audit_level(level) @auditor.set_audit_level(level) rescue ArgumentError $stderr.puts 'Invalid auditing level' raise end def debug(data, flow_identifier = nil) enqueue(:debug, format(:debug, prepend_caller_information(data), flow_identifier)) end def info(data, flow_identifier = nil) enqueue(:info, format(:info, prepend_caller_information(data), flow_identifier)) end def warn(data, flow_identifier = nil) enqueue(:warn, format(:warn, prepend_caller_information(data), flow_identifier)) end def error(data, flow_identifier = nil) enqueue(:error, format(:error, prepend_caller_information(data), flow_identifier)) end def fatal(data, flow_identifier = nil) enqueue(:fatal, format(:fatal, prepend_caller_information(data), flow_identifier)) end def <<(data, flow_identifier = nil) enqueue(:info, format(:info, prepend_caller_information(data), flow_identifier)) end def detailed_status detail = basic_status_detail detail = detail.merge(verbose_status_detail) if @configuration['verbose_detail'] detail end def flush @worker.flush end private def prepend_caller_information(data) if 'true' == @configuration['add_caller_source_location'] caller_key_value_pair = SoarAuditingFormatter::Formatter.optional_field_format("caller_source_location","#{caller_locations(2,1)[0]}") data = "#{caller_key_value_pair} #{data}" end data end def install_at_exit_handler if 'true' == @configuration['install_exit_handler'] Kernel.at_exit do exit_cleanup end end end def exit_cleanup(exception = nil) audit_exception_message(exception) if exception info("Application exit",@startup_flow_id) flush end def audit_exception_message(exception) exception_message = "#{exception.class}: #{exception.message}" exception_message = exception_message + ":\n\t" + exception.backtrace.join("\n\t") if ENV['RACK_ENV'] == 'development' fatal(exception_message,@startup_flow_id) end def enqueue(level, data) @worker.enqueue(level, data) @enqueued_audit_events += 1 rescue AuditingOverflowError increase_buffer_overflow_count $stderr.puts "Audit buffer full, unable to audit event : #{level} : #{data}" end def increase_buffer_overflow_count @buffer_overflow_count += 1 end def validate_provider_configuration(configuration) raise 'Missing auditing level' if configuration['level'].nil? end def format(level, data, flow_identifier) SoarAuditingFormatter::Formatter.format(level,@service_identifier,flow_identifier,Time.now.utc.iso8601(3),data) end def create_auditing_worker @worker = AuditingWorker.new @worker.configure(queue_worker_configuration: @configuration['queue_worker'], auditor_audit_method: method(:super_class_caller)) @worker.start end def super_class_caller(level, data) send("super_#{level}",data) end def create_auditors(configuration) auditor_factory = SoarConfiguredFactory::ConfiguredFactory.new(configuration['auditors']) auditors = {} configuration['auditors'].each do |auditor_name, auditor_configuration| raise 'Missing auditor configuration' if auditor_configuration.nil? auditor = create_auditor(auditor_factory,auditor_name) auditors[auditor] = { 'name' => auditor_name, 'nfrs' => auditor_configuration['nfrs'] } end auditors rescue $stderr.puts 'Failure initializing auditor' raise end def create_auditor(auditor_factory,auditor_name) auditor_factory.create(auditor_name) rescue $stderr.puts 'Invalid auditor configuration' raise end def initialize_metrics @startup_timestamp = Time.now.utc.iso8601(3) @enqueued_audit_events = 0 end def verbose_status_detail { 'worker' => { 'dequeued_audits' => @worker.dequeued_audits, 'successful_audits' => @worker.successful_audits, 'failed_audit_attempts' => @worker.failed_audit_attempts, 'latest_successful_audit_timespan' => @worker.latest_successful_audit_timespan, 'latest_successful_audit_timestamp' => @worker.latest_successful_audit_timestamp, 'latest_failed_audit_timestamp' => @worker.latest_failed_audit_timestamp, 'latest_failed_audit_error_message' => @worker.latest_failed_audit_error_message } } end def basic_status_detail { 'audit_buffer_overflows' => @buffer_overflow_count, 'enqueued_audit_events' => @enqueued_audit_events, 'startup_flow_id' => @startup_flow_id, 'startup_timestamp' => @startup_timestamp } end end end