diff --git a/app/models/miq_server/environment_management.rb b/app/models/miq_server/environment_management.rb index ffcb692d2f3..d2c05513d74 100644 --- a/app/models/miq_server/environment_management.rb +++ b/app/models/miq_server/environment_management.rb @@ -1,4 +1,3 @@ -require 'miq_apache' require 'linux_admin' module MiqServer::EnvironmentManagement diff --git a/app/models/mixins/miq_web_server_worker_mixin.rb b/app/models/mixins/miq_web_server_worker_mixin.rb index fc99e1d5798..fe8fe8490b9 100644 --- a/app/models/mixins/miq_web_server_worker_mixin.rb +++ b/app/models/mixins/miq_web_server_worker_mixin.rb @@ -1,4 +1,3 @@ -require 'miq_apache' class NoFreePortError < StandardError; end module MiqWebServerWorkerMixin diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 23c41d250b7..fcc2f3011db 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,25 @@ { "ignored_warnings": [ + { + "warning_type": "Command Injection", + "warning_code": 14, + "fingerprint": "14cf1baf5a62a0f109a2deb71920db195735f3054b031e50513d433f1566a670", + "check_name": "Execute", + "message": "Possible command injection", + "file": "lib/miq_apache/control.rb", + "line": 55, + "link": "http://brakemanscanner.org/docs/warning_types/command_injection/", + "code": "system(\"kill -WINCH #{`pgrep -P 1 httpd`.chomp.to_i}\")", + "render_path": null, + "location": { + "type": "method", + "class": "MiqApache::Control", + "method": "s(:self).stop" + }, + "user_input": "`pgrep -P 1 httpd`.chomp.to_i", + "confidence": "Medium", + "note": "The chomp.to_i ensures we get a number and we protect against 0 with a conditional. The only other possible avenue for attack is if the attacker could replace pgrep, but then they already have root access, so it's a moot point." + }, { "warning_type": "File Access", "warning_code": 16, @@ -7,7 +27,7 @@ "check_name": "FileAccess", "message": "Model attribute used in file name", "file": "app/models/miq_report/import_export.rb", - "line": 61, + "line": 80, "link": "http://brakemanscanner.org/docs/warning_types/file_access/", "code": "YAML.load_file(MiqReport.view_yaml_filename(db, current_user, options))", "render_path": null, @@ -101,6 +121,6 @@ "note": "Temporarily skipped, found in new brakeman version" } ], - "updated": "2017-05-30 16:04:08 -0700", - "brakeman_version": "3.6.2" + "updated": "2017-07-11 17:18:49 -0400", + "brakeman_version": "3.7.0" } diff --git a/lib/miq_apache.rb b/lib/miq_apache.rb new file mode 100644 index 00000000000..62f2c636a2a --- /dev/null +++ b/lib/miq_apache.rb @@ -0,0 +1,2 @@ +require_relative 'miq_apache/config' +require_relative 'miq_apache/control' diff --git a/lib/miq_apache/config.rb b/lib/miq_apache/config.rb new file mode 100644 index 00000000000..fe783f93f7f --- /dev/null +++ b/lib/miq_apache/config.rb @@ -0,0 +1,17 @@ +module MiqApache + DEFAULT_ROOT_DIR = '/' + DEFAULT_SERVICE_NAME = 'httpd' + DEFAULT_PACKAGE_NAME = 'httpd' + + def self.root_dir + ENV.fetch('MIQ_APACHE_ROOT_DIR', DEFAULT_ROOT_DIR) + end + + def self.service_name + ENV.fetch('MIQ_APACHE_SERVICE_NAME', DEFAULT_SERVICE_NAME) + end + + def self.package_name + ENV.fetch('MIQ_APACHE_PACKAGE_NAME', DEFAULT_PACKAGE_NAME) + end +end diff --git a/lib/miq_apache/control.rb b/lib/miq_apache/control.rb new file mode 100644 index 00000000000..00601dbe311 --- /dev/null +++ b/lib/miq_apache/control.rb @@ -0,0 +1,73 @@ +require 'fileutils' +require 'logger' +require 'active_support/core_ext/class/attribute_accessors' +require 'util/runcmd' +require 'util/extensions/miq-array' + +module MiqApache + # Abstract Apache Error Class + class Error < RuntimeError; end + + ################################################################### + # + # http://httpd.apache.org/docs/2.2/programs/apachectl.html + # + ################################################################### + class Control + APACHE_CONTROL_LOG = '/var/www/miq/vmdb/log/apache/miq_apache.log' + + def self.restart + ################################################################### + # Gracefully restarts the Apache httpd daemon. If the daemon is not running, it is started. + # This differs from a normal restart in that currently open connections are not aborted. + # A side effect is that old log files will not be closed immediately. This means that if + # used in a log rotation script, a substantial delay may be necessary to ensure that the + # old log files are closed before processing them. This command automatically checks the + # configuration files as in configtest before initiating the restart to make sure Apache + # doesn't die. + # + # Command line: apachectl graceful + ################################################################### + # + # FIXME: apache doesn't re-read the proxy balancer members on a graceful restart, so do a graceful stop and start + # system('apachectl graceful') + # http://www.gossamer-threads.com/lists/apache/users/383770 + # https://issues.apache.org/bugzilla/show_bug.cgi?id=45950 + # https://issues.apache.org/bugzilla/show_bug.cgi?id=39811 + # https://issues.apache.org/bugzilla/show_bug.cgi?id=44736 + # https://issues.apache.org/bugzilla/show_bug.cgi?id=42621 + + stop + start + end + + def self.start + if ENV["CONTAINER"] + system("/usr/sbin/httpd -DFOREGROUND &") + else + run_apache_cmd 'start' + end + end + + def self.stop + if ENV["CONTAINER"] + pid = `pgrep -P 1 httpd`.chomp.to_i + system("kill -WINCH #{pid}") if pid > 0 + else + run_apache_cmd 'stop' + end + end + + private + + def self.run_apache_cmd(command) + Dir.mkdir(File.dirname(APACHE_CONTROL_LOG)) unless File.exist?(File.dirname(APACHE_CONTROL_LOG)) + begin + cmd = "apachectl #{command}" + res = MiqUtil.runcmd(cmd) + rescue => err + $log.warn("MIQ(MiqApache::Control.run_apache_cmd) Apache command #{command} with result: #{res} failed with error: #{err}") if $log + end + end + end +end diff --git a/spec/lib/miq_apache/control_spec.rb b/spec/lib/miq_apache/control_spec.rb new file mode 100644 index 00000000000..5576da0a67d --- /dev/null +++ b/spec/lib/miq_apache/control_spec.rb @@ -0,0 +1,59 @@ +describe MiqApache::Control do + it "should run_apache_cmd with start when calling start" do + expect(MiqApache::Control).to receive(:run_apache_cmd).with('start') + MiqApache::Control.start + end + + it "should run_apache_cmd with graceful-stop and start when calling restart with graceful true" do + expect(MiqApache::Control).to receive(:run_apache_cmd).with('stop') + expect(MiqApache::Control).to receive(:run_apache_cmd).with('start') + MiqApache::Control.restart + end + + it "should run_apache_cmd with graceful-stop when calling stop with graceful true" do + expect(MiqApache::Control).to receive(:run_apache_cmd).with('stop') + MiqApache::Control.stop + end + + it "should make the apache control log's directory if missing when calling run_apache_cmd" do + allow(File).to receive(:exist?).and_return(false) + expect(Dir).to receive(:mkdir).with(File.dirname(MiqApache::Control::APACHE_CONTROL_LOG)) + allow(MiqUtil).to receive(:runcmd) + MiqApache::Control.run_apache_cmd("start") + end + + it "should not make the apache control log's directory if it exists when calling run_apache_cmd" do + allow(File).to receive(:exist?).and_return(true) + expect(Dir).to receive(:mkdir).with(File.dirname(MiqApache::Control::APACHE_CONTROL_LOG)).never + allow(MiqUtil).to receive(:runcmd) + MiqApache::Control.run_apache_cmd("start") + end + + it "should build cmdline when calling run_apache_cmd with start" do + cmd = "start" + allow(File).to receive(:exist?).and_return(true) + $log = Logger.new(STDOUT) unless $log + allow($log).to receive(:debug?).and_return(false) + expect(MiqUtil).to receive(:runcmd).with("apachectl #{cmd}") + MiqApache::Control.run_apache_cmd("start") + end + + it "should build cmdline when calling run_apache_cmd with start in debug mode if $log is debug" do + cmd = "start" + allow(File).to receive(:exist?).and_return(true) + $log = Logger.new(STDOUT) unless $log + allow($log).to receive(:debug?).and_return(true) + expect(MiqUtil).to receive(:runcmd).with("apachectl #{cmd}") + MiqApache::Control.run_apache_cmd("start") + end + + it "should log a warning when calling run_apache_cmd with start that raises an error" do + cmd = "start" + allow(File).to receive(:exist?).and_return(true) + $log = Logger.new(STDOUT) unless $log + allow($log).to receive(:debug?).and_return(false) + allow(MiqUtil).to receive(:runcmd).and_raise("warn") + expect($log).to receive(:warn) + MiqApache::Control.run_apache_cmd("start") + end +end