Tests plus faciles à manipuler

This commit is contained in:
Jérémy Lecour 2017-02-01 23:32:41 +01:00
parent 426b944b58
commit 979f3563f6
2 changed files with 121 additions and 63 deletions

View File

@ -3,96 +3,59 @@ require 'mechanize'
class SecurityTest < Minitest::Test class SecurityTest < Minitest::Test
include WebserverHelper include WebserverHelper
include SSLHelper
def domain
"ssl.evolix.net".freeze
end
def test_certificate_level def test_certificate_level
level = "intermediate" level = "intermediate"
output = `#{analyze_cmd(domain, level)}` command = analyze_cmd(domain: domain, level: level)
output = `#{command}`
assert_match %r|has intermediate ssl/tls\nand complies with the '#{level}' level|, output, "Expected to comply with #{level} level :\n#{output.inspect}" assert_match %r|has intermediate ssl/tls\nand complies with the '#{level}' level|, output, "Expected to comply with #{level} level :\n#{output.inspect}"
refute_match %r|consider enabling OCSP Stapling|, output, 'Expected to have OCSP stapling enabled' refute_match %r|consider enabling OCSP Stapling|, output, 'Expected to have OCSP stapling enabled'
end end
def test_certificate def test_certificate
output = `#{check_ssl_cert_cmd(domain)}` options = {
domain: "ssl.evolix.net",
issuer: %Q("Let's Encrypt Authority X3"),
cn: "ssl.evolix.net",
}
command = check_ssl_cert_cmd(options)
output = `#{command}`
assert_match(/\ASSL_CERT OK/, output, output) assert_match(/\ASSL_CERT OK/, output, output)
end end
def test_accepts_tls_v1 def test_accepts_tls_v1
output = `#{openssl_verify_cmd(domain, "-tls1")}` command = openssl_verify_cmd(domain, "-tls1")
output = `#{command}`
assert_match(/Verify return code: 0 \(ok\)/, output, "Expected to accept TLSv1") assert_match(/Verify return code: 0 \(ok\)/, output, "Expected to accept TLSv1")
end end
def test_refuse_ssl_v3 def test_refuse_ssl_v3
output = `#{openssl_verify_cmd(domain, "-ssl3")}` command = openssl_verify_cmd(domain, "-ssl3")
output = `#{command}`
assert_match(/sslv3 alert handshake failure/, output, "Expected to refuse SSLv3") assert_match(/sslv3 alert handshake failure/, output, "Expected to refuse SSLv3")
end end
def test_hsts_header def test_hsts_header
agent = Mechanize.new { |a| agent = Mechanize.new { |a|
a.follow_redirect = false a.follow_redirect = true
} }
page = agent.get("https://#{domain}") url = "https://#{domain}/"
page = agent.get(url)
context = "for #{url}"
assert_has_header("Strict-Transport-Security", page) assert_status_ok page, context
end assert_has_hsts page, context
assert_hsts_max_age "315360000", page, context
def check_ssl_cert_cmd(domain) refute_hsts_include_subdomains page, context
# check_ssl_cert is a Nagios plugin, usable outside of Nagios
# cf. https://trac.id.ethz.ch/projects/nagios_plugins/wiki/check_ssl_cert
args = [
"--rootcert", root_certificate,
"--openssl", openssl_path(:system),
"--issuer", %Q("Gandi Standard SSL CA 2"),
"--warning", 60,
"--critical", 30,
"--cn", %Q("*.example.com"),
"--host-cn",
"--ocsp",
"--host", domain,
].join(" ")
"vendor/check_ssl_cert/check_ssl_cert #{args}"
end
def analyze_cmd(domain, level = "intermediate")
# Cipherscan helps audit SSL configuration
# cf. https://github.com/jvehent/cipherscan
args = [
"-o", openssl_path(:local),
"-l", level,
"-t", domain
].join(' ')
"vendor/cipherscan/analyze.py #{args}"
end
def openssl_verify_cmd(domain, options = "")
args = [
"-CAfile", "#{root_certificate}",
"-connect", "#{domain}:443",
options,
"2>&1",
].join(" ")
"echo QUIT | #{openssl_path} s_client #{args}"
end
def openssl_path(variant = :system)
case variant
when :local
"vendor/cipherscan/openssl-darwin64"
else
`which openssl`.chop
end
end
def root_certificate
"test/certs/AddTrust_External_CA_Root.pem"
end end
end end

View File

@ -3,6 +3,63 @@ require "minitest/reporters"
Minitest::Reporters.use! Minitest::Reporters.use!
module SSLHelper
def check_ssl_cert_cmd(domain:, rootcert: nil, openssl: openssl_path(:system), issuer: nil, warning: 45, critical: 15, cn: nil, ocsp: true, host_cn: true)
# check_ssl_cert is a Nagios plugin, usable outside of Nagios
# cf. https://trac.id.ethz.ch/projects/nagios_plugins/wiki/check_ssl_cert
args = []
args.push("--host", domain)
args.push("--rootcert", rootcert) if rootcert
args.push("--openssl", openssl) if openssl
args.push("--issuer", issuer) if issuer
args.push("--warning", warning) if warning
args.push("--critical", critical) if critical
args.push("--cn", cn) if cn
args.push("--ocsp") if ocsp
args.push("--host-cn") if host_cn
"vendor/check_ssl_cert/check_ssl_cert #{args.join(" ")}"
end
def analyze_cmd(domain:, level: "intermediate", openssl: openssl_path(:system))
# Cipherscan helps audit SSL configuration
# cf. https://github.com/jvehent/cipherscan
args = [
"-o", openssl,
"-l", level,
"-t", domain
]
"vendor/cipherscan/analyze.py #{args.join(' ')}"
end
def openssl_verify_cmd(domain, options = "")
args = [
"-CAfile", "#{root_certificate}",
"-connect", "#{domain}:443",
options,
"2>&1",
]
"echo QUIT | #{openssl_path} s_client #{args.join(' ')}"
end
def openssl_path(variant = :system)
case variant
when :local
"vendor/cipherscan/openssl-darwin64"
else
`which openssl`.chop
end
end
def root_certificate
"test/certs/AddTrust_External_CA_Root.pem"
end
end
module WebserverHelper module WebserverHelper
def message_with_context(message, context = nil) def message_with_context(message, context = nil)
@ -91,10 +148,29 @@ module WebserverHelper
assert_has_last_modified page, context assert_has_last_modified page, context
end end
# def assert_hsts(page, context = nil)
# assert_status_ok page, context
# assert_hsts_max_age "315360000", page, context
# assert_public page, context
# refute_hsts_include_subdomains page, context
# end
def assert_has_header(header, page, context = nil) def assert_has_header(header, page, context = nil)
assert page.response.key?(header), message_with_context("Expected to find '#{header}' header".freeze, context) assert page.response.key?(header), message_with_context("Expected to find '#{header}' header".freeze, context)
end end
def assert_has_hsts(page, context = nil)
assert_includes page.response.keys, "strict-transport-security", message_with_context("Expected to find an Strict-Transport-Security header", context)
end
def assert_hsts_max_age(expected, page, context = nil)
assert_equal expected, hsts_max_age(page), message_with_context("Expected Strict-Transport-Security 'max-age' directive to be #{expected}", context)
end
def assert_hsts_include_subdomains(page, context = nil)
refute hsts_include_subdomains?(page), message_with_context("Expected not to find Strict-Transport-Security 'includeSubdomains' directive", context)
end
def assert_has_etag(page, context = nil) def assert_has_etag(page, context = nil)
assert_includes page.response.keys, "etag", message_with_context("Expected to find an ETag header", context) assert_includes page.response.keys, "etag", message_with_context("Expected to find an ETag header", context)
end end
@ -159,6 +235,25 @@ module WebserverHelper
header(page, "status".freeze) header(page, "status".freeze)
end end
def hsts_header(page)
header(page, "strict-transport-security".freeze)
end
def hsts_directives(page)
hsts_header(page).downcase.split(',').map(&:strip)
end
def hsts_include_subdomains?(page)
cache_control_directives(page).include?("includeSubdomains".freeze)
end
def hsts_max_age(page)
pattern = /\Amax-age\s?=\s?(\d+)\Z/
if found = hsts_directives(page).detect("") { |v| pattern =~ v }
found[pattern, 1]
end
end
def etag_header(page) def etag_header(page)
header(page, "etag".freeze) header(page, "etag".freeze)
end end