diff --git a/app/models/check.rb b/app/models/check.rb index de2d32d..9368a0d 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -101,6 +101,7 @@ class Check < ApplicationRecord return unless saved_changes.key?("domain") WhoisSyncJob.perform_later(id) if domain? + SSLSyncJob.perform_later(id) if ssl? end def reset_notifications diff --git a/app/services/ssl.rb b/app/services/ssl.rb index 95af40f..ee1d8d2 100644 --- a/app/services/ssl.rb +++ b/app/services/ssl.rb @@ -34,6 +34,7 @@ module SSL def run_command command = system_klass.new(check_http_path, check_http_args, logger: logger) + result = command.execute unless result.exit_status.zero? @@ -54,11 +55,22 @@ module SSL def check_http_args [ - configuration.check_http_args.presence, - "-H '#{domain}'", + "-C 0", # enable SSL mode without any delay warning + "-H", # check_http does not works with fully quoted arg (check_http "-H myhost.org") + domain, + *custom_check_http_args, ].compact end + def custom_check_http_args + return nil unless configuration.check_http_args.present? + + fail SSLConfigurationError, "check_http_args option must be an array of argument." \ + unless configuration.check_http_args.is_a?(Array) + + configuration.check_http_args + end + def default_configuration OpenStruct.new(Rails.configuration.chexpire.fetch("checks_ssl") { {} }) end diff --git a/app/services/ssl/errors.rb b/app/services/ssl/errors.rb index 6982c62..fe100c9 100644 --- a/app/services/ssl/errors.rb +++ b/app/services/ssl/errors.rb @@ -2,6 +2,7 @@ module SSL class Error < StandardError; end class SSLCommandError < Error; end + class SSLConfigurationError < Error; end class ParserError < Error; end class DomainNotMatchError < ParserError; end diff --git a/app/services/ssl/parser.rb b/app/services/ssl/parser.rb index 68dffe8..39ff46b 100644 --- a/app/services/ssl/parser.rb +++ b/app/services/ssl/parser.rb @@ -17,7 +17,7 @@ module SSL end def parse(raw) - fail DomainNotMatchError unless match_domain?(raw) + # fail DomainNotMatchError unless match_domain?(raw) # currently disabled match = raw.match(DATE_REGEX) @@ -33,8 +33,14 @@ module SSL raise end - def match_domain?(raw) - raw.match(/\b#{domain}\b/).present? + def match_domain?(raw, tested_domain = domain) + return true if raw.match(/\b#{tested_domain}\b/).present? + parts = tested_domain.split(".") + + return false if parts.count <= 2 + + parts.shift + match_domain?(raw, parts.join(".")) end def build_response(match) diff --git a/config/chexpire.example.yml b/config/chexpire.example.yml index b322b96..324a5d7 100644 --- a/config/chexpire.example.yml +++ b/config/chexpire.example.yml @@ -8,8 +8,8 @@ default: &default long_term: 60 long_term_frequency: 10 checks_ssl: - check_http_path: "" - check_http_args: "" + check_http_path: # default to check_http in $PATH) + check_http_args: # array of arguments appended to defaults: -C 0 -H $HOSTNAME. development: <<: *default diff --git a/config/chexpire.test.yml b/config/chexpire.test.yml index 2a72a7b..6af09a8 100644 --- a/config/chexpire.test.yml +++ b/config/chexpire.test.yml @@ -9,5 +9,5 @@ test: long_term: 60 long_term_frequency: 10 checks_ssl: - check_http_path: "" - check_http_args: "" + check_http_path: + check_http_args: diff --git a/test/fixtures/files/ssl/wildcard.domain.org.txt b/test/fixtures/files/ssl/wildcard.domain.org.txt new file mode 100644 index 0000000..cc7a8d1 --- /dev/null +++ b/test/fixtures/files/ssl/wildcard.domain.org.txt @@ -0,0 +1 @@ +OK - Certificate 'domain.org' will expire on Sat 10 Jun 2028 09:14:18 AM GMT +0000. diff --git a/test/jobs/ssl_sync_job_test.rb b/test/jobs/ssl_sync_job_test.rb index 0a55cdb..30b66a4 100644 --- a/test/jobs/ssl_sync_job_test.rb +++ b/test/jobs/ssl_sync_job_test.rb @@ -63,6 +63,6 @@ class SSLSyncJobTest < ActiveJob::TestCase end def expected_command_arg(domain) - ["-H '#{domain}'"] + ["-C 0", "-H", domain] end end diff --git a/test/services/check_ssl_processor_test.rb b/test/services/check_ssl_processor_test.rb index db6fa3c..b294a7c 100644 --- a/test/services/check_ssl_processor_test.rb +++ b/test/services/check_ssl_processor_test.rb @@ -10,7 +10,7 @@ class CheckSSLProcessorTest < ActiveSupport::TestCase check = create(:check, :ssl, :nil_dates, domain: domain) response = file_fixture("ssl/ssl0.domain.org.txt").read - mock_system_command("check_http", ["-H '#{domain}'"], stdout: response) do + mock_system_command("check_http", ["-C 0", "-H", domain], stdout: response) do @processor.send(:process, check) end diff --git a/test/services/ssl/parser_test.rb b/test/services/ssl/parser_test.rb index 05566d6..8c519a6 100644 --- a/test/services/ssl/parser_test.rb +++ b/test/services/ssl/parser_test.rb @@ -24,14 +24,32 @@ module SSL assert response.expire_at.utc? end - test "should raises DomainNotMatchError when parsed text does not match the domain" do - parser = Parser.new("anotherdomain.fr") - output = file_fixture("ssl/ssl1.domain.org.txt").read + # test "should raises DomainNotMatchError when parsed text does not match the domain" do + # parser = Parser.new("anotherdomain.fr") + # output = file_fixture("ssl/ssl1.domain.org.txt").read + # + # assert_raises DomainNotMatchError do + # parser.parse(output) + # end + # end - assert_raises DomainNotMatchError do - parser.parse(output) - end + test "should accept responses for wildcard certificates" do + parser = Parser.new("ssl1.domain.org") + output = file_fixture("ssl/wildcard.domain.org.txt").read + + response = parser.parse(output) + + assert_equal Time.new(2028, 6, 10, 9, 14, 18, 0), response.expire_at + assert response.expire_at.utc? + + parser = Parser.new("deep.ssl1.domain.org") + output = file_fixture("ssl/wildcard.domain.org.txt").read + + response = parser.parse(output) + + assert_equal Time.new(2028, 6, 10, 9, 14, 18, 0), response.expire_at end + test "should raises InvalidResponseError when check response is not matched" do parser = Parser.new("ssl100.invalid.org") output = file_fixture("ssl/ssl100.invalid.org.txt").read diff --git a/test/services/ssl_test.rb b/test/services/ssl_test.rb index 5b98396..b544781 100644 --- a/test/services/ssl_test.rb +++ b/test/services/ssl_test.rb @@ -7,7 +7,7 @@ module SSL test "should run the command, return the result" do result = OpenStruct.new(exit_status: 0) - mock_system_klass("check_http", ["-H 'example.org'"], result) do |system_klass| + mock_system_klass("check_http", ["-C 0", "-H", "example.org"], result) do |system_klass| service = Service.new("example.org", system_klass: system_klass) assert_equal result, service.run_command end @@ -16,7 +16,7 @@ module SSL test "should raise an exception if exit status > 0" do result = OpenStruct.new(exit_status: 1) - mock_system_klass("check_http", ["-H 'example.org'"], result) do |system_klass| + mock_system_klass("check_http", ["-C 0", "-H", "example.org"], result) do |system_klass| service = Service.new("example.org", system_klass: system_klass) assert_raises SSLCommandError do @@ -37,21 +37,31 @@ module SSL test "should uses the command line arguments of the configuration" do result = OpenStruct.new(exit_status: 0) - config = OpenStruct.new(check_http_args: "-f follow -I 127.0.0.1") + config = OpenStruct.new(check_http_args: ["-f", "-I 127.0.0.1"]) - expected_args = ["-f follow -I 127.0.0.1", "-H 'example.org'"] + expected_args = ["-C 0", "-H", "example.org", "-f", "-I 127.0.0.1"] mock_system_klass("check_http", expected_args, result) do |system_klass| service = Service.new("example.org", configuration: config, system_klass: system_klass) assert_equal result, service.run_command end end + test "should raise an error when check_http_args is not an array" do + black_hole = Naught.build(&:black_hole) + config = OpenStruct.new(check_http_args: "-f") + + assert_raises SSLConfigurationError do + service = Service.new("example.org", configuration: config, system_klass: black_hole) + service.run_command + end + end + test "should uses the program path from the configuration" do result = OpenStruct.new(exit_status: 0) config = OpenStruct.new(check_http_path: "/usr/local/custom/path") - mock_system_klass("/usr/local/custom/path", ["-H 'example.org'"], result) do |system_klass| - service = Service.new("example.org", configuration: config, system_klass: system_klass) + mock_system_klass("/usr/local/custom/path", ["-C 0", "-H", "example.org"], result) do |sys| + service = Service.new("example.org", configuration: config, system_klass: sys) assert_equal result, service.run_command end end