Class: HybridPlatformsConductor::HpcPlugins::Test::Vulnerabilities
- Inherits:
-
TestOnlyRemoteNode
- Object
- Plugin
- Test
- TestOnlyRemoteNode
- HybridPlatformsConductor::HpcPlugins::Test::Vulnerabilities
- Defined in:
- lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb
Overview
Test that the node has not known vulnerabilities. Check this by using OVAL files published by vendors. For example, RedHat publishes them here: www.redhat.com/security/data/oval/v2/RHEL7/ This test uses an oval.json file stored in the OS images folder, having the following structure:
-
urls (Array<String>): List of URLs pointing to OVAL files [default: []]
Each URL can be directly an XML file, either raw or compressed with .gz or .bz2.
-
repo_urls (Array<String>): List of URLs pointing to repositories of OVAL files [default: []]
The last HTML link of each repo URL is followed until an OVAL file is found. Each final OVAL URL can be directly an XML file, either raw or compressed with .gz or .bz2. This is useful to follow repository links, such as jFrog or web servers serving common file systems structure storing several versions of the OVAL file.
-
reported_severities (Array<String> or nil): List of severities to report, if any (use Unknown when the severity is not known), or nil for all [default: nil]
Constant Summary collapse
- KNOWN_COMPRESSIONS =
Known compression methods, per file extension, and their corresponding uncompress bash script
{ bz2: { cmd: proc { |file| "if [ ! -f \"#{File.basename(file, '.bz2')}\" ] ; then bunzip2 \"#{file}\" ; fi" }, packages: ['bzip2'] }, gz: { cmd: proc { |file| "if [ ! -f \"#{File.basename(file, '.gz')}\" ] ; then gunzip \"#{file}\" ; fi" }, packages: ['gzip'] } }
Constants included from LoggerHelpers
LoggerHelpers::LEVELS_MODIFIERS, LoggerHelpers::LEVELS_TO_STDERR
Instance Attribute Summary
Attributes inherited from Test
#errors, #expected_failure, #name, #node, #platform
Instance Method Summary collapse
-
#test_on_node ⇒ Object
Check my_test_plugin.rb.sample documentation for signature details.
Methods inherited from TestOnlyRemoteNode
Methods inherited from Test
#assert_equal, #assert_match, #error, #executed, #executed?, #initialize, only_on_nodes, only_on_platforms, #to_s
Methods inherited from Plugin
extend_config_dsl_with, #initialize, valid?
Methods included from LoggerHelpers
#err, #init_loggers, #log_component=, #log_debug?, #log_level=, #out, #section, #set_loggers_format, #stderr_device, #stderr_device=, #stderr_displayed?, #stdout_device, #stdout_device=, #stdout_displayed?, #stdouts_to_s, #with_progress_bar
Constructor Details
This class inherits a constructor from HybridPlatformsConductor::Test
Instance Method Details
#test_on_node ⇒ Object
Check my_test_plugin.rb.sample documentation for signature details.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb', line 36 def test_on_node # Get the image name for this node image = @nodes_handler.get_image_of(@node).to_sym # Find if we have such an image registered if @config.known_os_images.include?(image) oval_file = "#{@config.os_image_dir(image)}/oval.json" if File.exist?(oval_file) oval_info = JSON.parse(File.read(oval_file)) # Get all URLs urls = oval_info['urls'] || [] urls.concat( (oval_info['repo_urls'] || []).map do |artifactory_url| # Follow the last link recursively until we find a .xml or compressed file current_url = artifactory_url loop do current_url = "#{current_url}#{current_url.end_with?('/') ? '' : '/'}#{Nokogiri::HTML.parse(URI.open(current_url)).css('a').last['href']}" break if current_url.end_with?('.xml') || KNOWN_COMPRESSIONS.keys.any? { |file_ext| current_url.end_with?(".#{file_ext}") } log_debug "Follow last link to #{current_url}" end current_url end ) # TODO: Access the user correctly when the user notion will be moved out of the ssh connector sudo = @deployer.instance_variable_get(:@actions_executor).connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(@node)} " Hash[urls.map do |url| # 1. Get the OVAL file on the node to be tested (uncompress it if needed) # 2. Make sure oscap is installed # 3. Generate the report for this OVAL file using oscap # 4. Get back the report here to analyze it local_oval_file = File.basename(url) uncompress_cmds = [] packages_to_install = [] KNOWN_COMPRESSIONS.each do |file_ext, compress_info| file_ending = ".#{file_ext}" if local_oval_file.end_with?(file_ending) uncompress_cmds << compress_info[:cmd].call(local_oval_file) packages_to_install.concat(compress_info[:packages]) local_oval_file = File.basename(local_oval_file, file_ending) end end cmds = <<~EOS set -e #{ case image when :centos_7 "#{sudo}yum install -y wget openscap-scanner #{packages_to_install.join(' ')}" when :debian_9 "#{sudo}apt install -y wget libopenscap8 #{packages_to_install.join(' ')}" when :debian_10 # On Debian 10 we have to compile it from sources, as the packaged official version has core dumps. # cf https://www.mail-archive.com/[email protected]/msg1688223.html # TODO: Remove this Debian 10 specificity when the official libopenscap8 will be corrected <<~EOS2 if [ ! -x "$(command -v oscap)" ] || [ "$(oscap --version | head -n 1 | awk '{print $6}')" != "1.3.4" ]; then rm -rf openscap git clone --recurse-submodules https://github.com/OpenSCAP/openscap.git cd openscap #{sudo}apt install -y cmake libdbus-1-dev libdbus-glib-1-dev libcurl4-openssl-dev libgcrypt20-dev libselinux1-dev libxslt1-dev libgconf2-dev libacl1-dev libblkid-dev libcap-dev libxml2-dev libldap2-dev libpcre3-dev python-dev swig libxml-parser-perl libxml-xpath-perl libperl-dev libbz2-dev librpm-dev g++ libapt-pkg-dev libyaml-dev cd build cmake ../ make #{sudo}make install fi #{sudo}apt install -y wget #{packages_to_install.join(' ')} EOS2 else raise "Non supported image: #{image}. Please adapt this test's code." end } rm -rf hpc_vulnerabilities_test mkdir -p hpc_vulnerabilities_test cd hpc_vulnerabilities_test wget -N #{url} #{uncompress_cmds.join("\n")} #{sudo}oscap oval eval --skip-valid --results "#{local_oval_file}.results.xml" "#{local_oval_file}" echo "===== RESULTS =====" cat "#{local_oval_file}.results.xml" cd .. EOS [ cmds, { validator: proc do |stdout| idx_results = stdout.index('===== RESULTS =====') if idx_results.nil? error 'No results given by the oscap run', stdout.join("\n") else results = Nokogiri::XML(stdout[idx_results + 1..-1].join("\n")) results.remove_namespaces! oval_definitions = results.css('oval_results oval_definitions definitions definition') results.css('results system definitions definition').each do |definition_xml| if definition_xml['result'] == 'true' # Just found an OVAL item to be patched. definition_id = definition_xml['definition_id'] oval_definition = oval_definitions.find { |el| el['id'] == definition_id } # We don't forcefully want to report all missing patches. Only the most important ones. severity = oval_definition.css('metadata advisory severity').text severity = 'Unknown' if severity.empty? if !oval_info.key?('reported_severities') || oval_info['reported_severities'].include?(severity) # Only consider the first line of the description, as sometimes it's very long error "Non-patched #{severity} vulnerability found: #{oval_definition.css('metadata title').text} - #{oval_definition.css('metadata description').text.split("\n").first}" end end end end end, # Increase timeout in case we have to install a lot of dependencies (like for Debian 10) timeout: 240 } ] end] else error "No OVAL file defined for image #{image} at #{oval_file}" {} end else error "Unknown OS image #{image} defined for node #{@node}" {} end end |