From 4a30a8aa32a759ca68950c9d586c0d33c52d982e Mon Sep 17 00:00:00 2001 From: Kris Shannon Date: Mon, 5 Aug 2019 18:17:01 +1000 Subject: [PATCH] Move some resources into Inspec::Resources module The `DhParams`, `EtcHosts`, `GrubConfig`, `IisAppPool`, and `SSL` resources were not properly located inside the `Inspec::Resources` module. Fixes: #4327 Signed-off-by: Kris Shannon --- lib/inspec/resources/dh_params.rb | 122 ++++---- lib/inspec/resources/etc_hosts.rb | 98 ++++--- lib/inspec/resources/grub_conf.rb | 420 ++++++++++++++------------- lib/inspec/resources/iis_app_pool.rb | 198 ++++++------- lib/inspec/resources/ssl.rb | 162 ++++++----- 5 files changed, 505 insertions(+), 495 deletions(-) diff --git a/lib/inspec/resources/dh_params.rb b/lib/inspec/resources/dh_params.rb index 467255532..2fdc24ccb 100644 --- a/lib/inspec/resources/dh_params.rb +++ b/lib/inspec/resources/dh_params.rb @@ -1,81 +1,83 @@ require "openssl" require "inspec/utils/file_reader" -class DhParams < Inspec.resource(1) - name "dh_params" - supports platform: "unix" - desc ' - Use the `dh_params` InSpec audit resource to test Diffie-Hellman (DH) - parameters. - ' +module Inspec::Resources + class DhParams < Inspec.resource(1) + name "dh_params" + supports platform: "unix" + desc ' + Use the `dh_params` InSpec audit resource to test Diffie-Hellman (DH) + parameters. + ' - example <<~EXAMPLE - describe dh_params('/path/to/file.dh_pem') do - it { should be_dh_params } - it { should be_valid } - its('generator') { should eq 2 } - its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' } - its('prime_length') { should eq 2048 } - its('pem') { should eq '-----BEGIN DH PARAMETERS...' } - its('text') { should eq 'PKCS#3 DH Parameters: (2048 bit)...' } + example <<~EXAMPLE + describe dh_params('/path/to/file.dh_pem') do + it { should be_dh_params } + it { should be_valid } + its('generator') { should eq 2 } + its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' } + its('prime_length') { should eq 2048 } + its('pem') { should eq '-----BEGIN DH PARAMETERS...' } + its('text') { should eq 'PKCS#3 DH Parameters: (2048 bit)...' } + end + EXAMPLE + + include FileReader + + def initialize(filename) + @dh_params_path = filename + @dh_params = OpenSSL::PKey::DH.new read_file_content(@dh_params_path) end - EXAMPLE - include FileReader + # it { should be_dh_params } + def dh_params? + !@dh_params.nil? + end - def initialize(filename) - @dh_params_path = filename - @dh_params = OpenSSL::PKey::DH.new read_file_content(@dh_params_path) - end + # its('generator') { should eq 2 } + def generator + return if @dh_params.nil? - # it { should be_dh_params } - def dh_params? - !@dh_params.nil? - end + @dh_params.g.to_i + end - # its('generator') { should eq 2 } - def generator - return if @dh_params.nil? + # its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' } + def modulus + return if @dh_params.nil? - @dh_params.g.to_i - end + "00:" + @dh_params.p.to_s(16).downcase.scan(/.{2}/).join(":") + end - # its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' } - def modulus - return if @dh_params.nil? + # its('pem') { should eq '-----BEGIN DH PARAMETERS...' } + def pem + return if @dh_params.nil? - "00:" + @dh_params.p.to_s(16).downcase.scan(/.{2}/).join(":") - end + @dh_params.to_pem + end - # its('pem') { should eq '-----BEGIN DH PARAMETERS...' } - def pem - return if @dh_params.nil? + # its('prime_length') { should be 2048 } + def prime_length + return if @dh_params.nil? - @dh_params.to_pem - end + @dh_params.p.num_bits + end - # its('prime_length') { should be 2048 } - def prime_length - return if @dh_params.nil? + # its('text') { should eq 'human-readable-text' } + def text + return if @dh_params.nil? - @dh_params.p.num_bits - end + @dh_params.to_text + end - # its('text') { should eq 'human-readable-text' } - def text - return if @dh_params.nil? + # it { should be_valid } + def valid? + return if @dh_params.nil? - @dh_params.to_text - end + @dh_params.params_ok? + end - # it { should be_valid } - def valid? - return if @dh_params.nil? - - @dh_params.params_ok? - end - - def to_s - "dh_params #{@dh_params_path}" + def to_s + "dh_params #{@dh_params_path}" + end end end diff --git a/lib/inspec/resources/etc_hosts.rb b/lib/inspec/resources/etc_hosts.rb index 80178cabd..5c65e853b 100644 --- a/lib/inspec/resources/etc_hosts.rb +++ b/lib/inspec/resources/etc_hosts.rb @@ -1,62 +1,64 @@ require "inspec/utils/parser" require "inspec/utils/file_reader" -class EtcHosts < Inspec.resource(1) - name "etc_hosts" - supports platform: "linux" - supports platform: "bsd" - supports platform: "windows" - desc 'Use the etc_hosts InSpec audit resource to find an - ip_address and its associated hosts' - example <<~EXAMPLE - describe etc_hosts.where { ip_address == '127.0.0.1' } do - its('ip_address') { should cmp '127.0.0.1' } - its('primary_name') { should cmp 'localhost' } - its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] } +module Inspec::Resources + class EtcHosts < Inspec.resource(1) + name "etc_hosts" + supports platform: "linux" + supports platform: "bsd" + supports platform: "windows" + desc 'Use the etc_hosts InSpec audit resource to find an + ip_address and its associated hosts' + example <<~EXAMPLE + describe etc_hosts.where { ip_address == '127.0.0.1' } do + its('ip_address') { should cmp '127.0.0.1' } + its('primary_name') { should cmp 'localhost' } + its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] } + end + EXAMPLE + + attr_reader :params + + include CommentParser + include FileReader + + DEFAULT_UNIX_PATH = "/etc/hosts".freeze + DEFAULT_WINDOWS_PATH = 'C:\windows\system32\drivers\etc\hosts'.freeze + + def initialize(hosts_path = nil) + content = read_file_content(hosts_path || default_hosts_file_path) + + @params = parse_conf(content.lines) end - EXAMPLE - attr_reader :params + FilterTable.create + .register_column(:ip_address, field: "ip_address") + .register_column(:primary_name, field: "primary_name") + .register_column(:all_host_names, field: "all_host_names") + .install_filter_methods_on_resource(self, :params) - include CommentParser - include FileReader + private - DEFAULT_UNIX_PATH = "/etc/hosts".freeze - DEFAULT_WINDOWS_PATH = 'C:\windows\system32\drivers\etc\hosts'.freeze + def default_hosts_file_path + inspec.os.windows? ? DEFAULT_WINDOWS_PATH : DEFAULT_UNIX_PATH + end - def initialize(hosts_path = nil) - content = read_file_content(hosts_path || default_hosts_file_path) + def parse_conf(lines) + lines.reject(&:empty?).reject(&comment?).map(&parse_data).map(&format_data) + end - @params = parse_conf(content.lines) - end + def comment? + parse_options = { comment_char: "#", standalone_comments: false } - FilterTable.create - .register_column(:ip_address, field: "ip_address") - .register_column(:primary_name, field: "primary_name") - .register_column(:all_host_names, field: "all_host_names") - .install_filter_methods_on_resource(self, :params) + ->(data) { parse_comment_line(data, parse_options).first.empty? } + end - private + def parse_data + ->(data) { [data.split[0], data.split[1], data.split[1..-1]] } + end - def default_hosts_file_path - inspec.os.windows? ? DEFAULT_WINDOWS_PATH : DEFAULT_UNIX_PATH - end - - def parse_conf(lines) - lines.reject(&:empty?).reject(&comment?).map(&parse_data).map(&format_data) - end - - def comment? - parse_options = { comment_char: "#", standalone_comments: false } - - ->(data) { parse_comment_line(data, parse_options).first.empty? } - end - - def parse_data - ->(data) { [data.split[0], data.split[1], data.split[1..-1]] } - end - - def format_data - ->(data) { %w{ip_address primary_name all_host_names}.zip(data).to_h } + def format_data + ->(data) { %w{ip_address primary_name all_host_names}.zip(data).to_h } + end end end diff --git a/lib/inspec/resources/grub_conf.rb b/lib/inspec/resources/grub_conf.rb index 724866aea..28cfad6ac 100644 --- a/lib/inspec/resources/grub_conf.rb +++ b/lib/inspec/resources/grub_conf.rb @@ -1,228 +1,230 @@ require "inspec/utils/simpleconfig" require "inspec/utils/file_reader" -class GrubConfig < Inspec.resource(1) - name "grub_conf" - supports platform: "unix" - desc "Use the grub_conf InSpec audit resource to test the boot config of Linux systems that use Grub." - example <<~EXAMPLE - describe grub_conf('/etc/grub.conf', 'default') do - its('kernel') { should include '/vmlinuz-2.6.32-573.7.1.el6.x86_64' } - its('initrd') { should include '/initramfs-2.6.32-573.el6.x86_64.img=1' } - its('default') { should_not eq '1' } - its('timeout') { should eq '5' } - end - - also check specific kernels - describe grub_conf('/etc/grub.conf', 'CentOS (2.6.32-573.12.1.el6.x86_64)') do - its('kernel') { should include 'audit=1' } - end - EXAMPLE - - include FileReader - - class UnknownGrubConfig < StandardError; end - - def initialize(path = nil, kernel = nil) - config_for_platform(path) - @content = read_file(@conf_path) - @kernel = kernel || "default" - rescue UnknownGrubConfig - skip_resource "The `grub_config` resource is not supported on your OS yet." - end - - def config_for_platform(path) - os = inspec.os - if os.redhat? || os[:name] == "fedora" - config_for_redhatish(path) - elsif os.debian? - @conf_path = path || "/boot/grub/grub.cfg" - @defaults_path = "/etc/default/grub" - @grubenv_path = "/boot/grub2/grubenv" - @version = "grub2" - elsif os[:name] == "amazon" - @conf_path = path || "/etc/grub.conf" - @version = "legacy" - else - raise UnknownGrubConfig - end - end - - def config_for_redhatish(path) - if inspec.os[:release].to_f < 7 - @conf_path = path || "/etc/grub.conf" - @version = "legacy" - else - @conf_path = path || "/boot/grub2/grub.cfg" - @defaults_path = "/etc/default/grub" - @grubenv_path = "/boot/grub2/grubenv" - @version = "grub2" - end - end - - def method_missing(name) - read_params[name.to_s] - end - - def to_s - "Grub Config" - end - - private - - ###################################################################### - # Grub2 This is used by all supported versions of Ubuntu and Rhel 7+ # - ###################################################################### - - def grub2_parse_kernel_lines(content, conf) - menu_entries = extract_menu_entries(content) - - if @kernel == "default" - default_menu_entry(menu_entries, conf["GRUB_DEFAULT"]) - else - menu_entries.find { |entry| entry["name"] == @kernel } - end - end - - def extract_menu_entries(content) - menu_entries = [] - - lines = content.split("\n") - lines.each_with_index do |line, index| - next unless line =~ /^menuentry\s+.*/ - - entry = {} - entry["insmod"] = [] - - # Extract name from menuentry line - capture_data = line.match(/(?:^|\s+).*menuentry\s*['|"](.*)['|"]\s*--/) - if capture_data.nil? || capture_data.captures[0].nil? - raise Inspec::Exceptions::ResourceFailed "Failed to extract menuentry name from #{line}" +module Inspec::Resources + class GrubConfig < Inspec.resource(1) + name "grub_conf" + supports platform: "unix" + desc "Use the grub_conf InSpec audit resource to test the boot config of Linux systems that use Grub." + example <<~EXAMPLE + describe grub_conf('/etc/grub.conf', 'default') do + its('kernel') { should include '/vmlinuz-2.6.32-573.7.1.el6.x86_64' } + its('initrd') { should include '/initramfs-2.6.32-573.el6.x86_64.img=1' } + its('default') { should_not eq '1' } + its('timeout') { should eq '5' } end - entry["name"] = capture_data.captures[0] + also check specific kernels + describe grub_conf('/etc/grub.conf', 'CentOS (2.6.32-573.12.1.el6.x86_64)') do + its('kernel') { should include 'audit=1' } + end + EXAMPLE - # Begin processing from index forward until a `}` line is met - lines.drop(index + 1).each do |mline| - break if mline =~ /^\s*}\s*$/ + include FileReader - case mline - when /(?:^|\s*)initrd.*/ - entry["initrd"] = mline.split(" ")[1] - when /(?:^|\s*)linux.*/ - entry["kernel"] = mline.split - when /(?:^|\s*)set root=.*/ - entry["root"] = mline.split("=")[1].tr("'", "") - when /(?:^|\s*)insmod.*/ - entry["insmod"] << mline.split(" ")[1] + class UnknownGrubConfig < StandardError; end + + def initialize(path = nil, kernel = nil) + config_for_platform(path) + @content = read_file(@conf_path) + @kernel = kernel || "default" + rescue UnknownGrubConfig + skip_resource "The `grub_config` resource is not supported on your OS yet." + end + + def config_for_platform(path) + os = inspec.os + if os.redhat? || os[:name] == "fedora" + config_for_redhatish(path) + elsif os.debian? + @conf_path = path || "/boot/grub/grub.cfg" + @defaults_path = "/etc/default/grub" + @grubenv_path = "/boot/grub2/grubenv" + @version = "grub2" + elsif os[:name] == "amazon" + @conf_path = path || "/etc/grub.conf" + @version = "legacy" + else + raise UnknownGrubConfig + end + end + + def config_for_redhatish(path) + if inspec.os[:release].to_f < 7 + @conf_path = path || "/etc/grub.conf" + @version = "legacy" + else + @conf_path = path || "/boot/grub2/grub.cfg" + @defaults_path = "/etc/default/grub" + @grubenv_path = "/boot/grub2/grubenv" + @version = "grub2" + end + end + + def method_missing(name) + read_params[name.to_s] + end + + def to_s + "Grub Config" + end + + private + + ###################################################################### + # Grub2 This is used by all supported versions of Ubuntu and Rhel 7+ # + ###################################################################### + + def grub2_parse_kernel_lines(content, conf) + menu_entries = extract_menu_entries(content) + + if @kernel == "default" + default_menu_entry(menu_entries, conf["GRUB_DEFAULT"]) + else + menu_entries.find { |entry| entry["name"] == @kernel } + end + end + + def extract_menu_entries(content) + menu_entries = [] + + lines = content.split("\n") + lines.each_with_index do |line, index| + next unless line =~ /^menuentry\s+.*/ + + entry = {} + entry["insmod"] = [] + + # Extract name from menuentry line + capture_data = line.match(/(?:^|\s+).*menuentry\s*['|"](.*)['|"]\s*--/) + if capture_data.nil? || capture_data.captures[0].nil? + raise Inspec::Exceptions::ResourceFailed "Failed to extract menuentry name from #{line}" end - end - menu_entries << entry - end + entry["name"] = capture_data.captures[0] - menu_entries - end + # Begin processing from index forward until a `}` line is met + lines.drop(index + 1).each do |mline| + break if mline =~ /^\s*}\s*$/ - def default_menu_entry(menu_entries, default) - # If the default entry isn't `saved` then a number is used as an index. - # By default this is `0`, which would be the first item in the list. - return menu_entries[default.to_i] unless default == "saved" - - grubenv_contents = inspec.file(@grubenv_path).content - - # The location of the grubenv file is not guaranteed. In the case that - # the file does not exist this will return the 0th entry. This will also - # return the 0th entry if InSpec lacks permission to read the file. Both - # of these reflect the default Grub2 behavior. - return menu_entries[0] if grubenv_contents.nil? - - default_name = SimpleConfig.new(grubenv_contents).params["saved_entry"] - default_entry = menu_entries.select { |k| k["name"] == default_name }[0] - return default_entry unless default_entry.nil? - - # It is possible for the saved entry to not be valid . For example, grubenv - # not being up to date. If so, the 0th entry is the default. - menu_entries[0] - end - - ################################################################### - # Grub1 aka legacy-grub config. Primarily used by Centos/Rhel 6.x # - ################################################################### - - def parse_kernel_lines(content, conf) - # Find all "title" lines and then parse them into arrays - menu_entry = 0 - lines = content.split("\n") - kernel_opts = {} - lines.each_with_index do |file_line, index| - next unless file_line =~ /^title.*/ - - current_kernel = file_line.split(" ", 2)[1] - lines.drop(index + 1).each do |kernel_line| - if kernel_line =~ /^\s.*/ - option_type = kernel_line.split(" ")[0] - line_options = kernel_line.split(" ").drop(1) - if (menu_entry == conf["default"].to_i && @kernel == "default") || current_kernel == @kernel - if option_type == "kernel" - kernel_opts["kernel"] = line_options - else - kernel_opts[option_type] = line_options[0] - end + case mline + when /(?:^|\s*)initrd.*/ + entry["initrd"] = mline.split(" ")[1] + when /(?:^|\s*)linux.*/ + entry["kernel"] = mline.split + when /(?:^|\s*)set root=.*/ + entry["root"] = mline.split("=")[1].tr("'", "") + when /(?:^|\s*)insmod.*/ + entry["insmod"] << mline.split(" ")[1] end - else - menu_entry += 1 - break end - end - end - kernel_opts - end - def read_file(config_file) - read_file_content(config_file) - end - - def read_params - return @params if defined?(@params) - - content = read_file(@conf_path) - - if @version == "legacy" - # parse the file - conf = SimpleConfig.new( - content, - multiple_values: true - ).params - # convert single entry arrays into strings - conf.each do |key, value| - if value.size == 1 - conf[key] = conf[key][0].to_s - end - end - kernel_opts = parse_kernel_lines(content, conf) - @params = conf.merge(kernel_opts) - end - - if @version == "grub2" - # read defaults - defaults = read_file(@defaults_path) - - conf = SimpleConfig.new( - defaults, - multiple_values: true - ).params - - # convert single entry arrays into strings - conf.each do |key, value| - if value.size == 1 - conf[key] = conf[key][0].to_s - end + menu_entries << entry end - kernel_opts = grub2_parse_kernel_lines(content, conf) - @params = conf.merge(kernel_opts) + menu_entries + end + + def default_menu_entry(menu_entries, default) + # If the default entry isn't `saved` then a number is used as an index. + # By default this is `0`, which would be the first item in the list. + return menu_entries[default.to_i] unless default == "saved" + + grubenv_contents = inspec.file(@grubenv_path).content + + # The location of the grubenv file is not guaranteed. In the case that + # the file does not exist this will return the 0th entry. This will also + # return the 0th entry if InSpec lacks permission to read the file. Both + # of these reflect the default Grub2 behavior. + return menu_entries[0] if grubenv_contents.nil? + + default_name = SimpleConfig.new(grubenv_contents).params["saved_entry"] + default_entry = menu_entries.select { |k| k["name"] == default_name }[0] + return default_entry unless default_entry.nil? + + # It is possible for the saved entry to not be valid . For example, grubenv + # not being up to date. If so, the 0th entry is the default. + menu_entries[0] + end + + ################################################################### + # Grub1 aka legacy-grub config. Primarily used by Centos/Rhel 6.x # + ################################################################### + + def parse_kernel_lines(content, conf) + # Find all "title" lines and then parse them into arrays + menu_entry = 0 + lines = content.split("\n") + kernel_opts = {} + lines.each_with_index do |file_line, index| + next unless file_line =~ /^title.*/ + + current_kernel = file_line.split(" ", 2)[1] + lines.drop(index + 1).each do |kernel_line| + if kernel_line =~ /^\s.*/ + option_type = kernel_line.split(" ")[0] + line_options = kernel_line.split(" ").drop(1) + if (menu_entry == conf["default"].to_i && @kernel == "default") || current_kernel == @kernel + if option_type == "kernel" + kernel_opts["kernel"] = line_options + else + kernel_opts[option_type] = line_options[0] + end + end + else + menu_entry += 1 + break + end + end + end + kernel_opts + end + + def read_file(config_file) + read_file_content(config_file) + end + + def read_params + return @params if defined?(@params) + + content = read_file(@conf_path) + + if @version == "legacy" + # parse the file + conf = SimpleConfig.new( + content, + multiple_values: true + ).params + # convert single entry arrays into strings + conf.each do |key, value| + if value.size == 1 + conf[key] = conf[key][0].to_s + end + end + kernel_opts = parse_kernel_lines(content, conf) + @params = conf.merge(kernel_opts) + end + + if @version == "grub2" + # read defaults + defaults = read_file(@defaults_path) + + conf = SimpleConfig.new( + defaults, + multiple_values: true + ).params + + # convert single entry arrays into strings + conf.each do |key, value| + if value.size == 1 + conf[key] = conf[key][0].to_s + end + end + + kernel_opts = grub2_parse_kernel_lines(content, conf) + @params = conf.merge(kernel_opts) + end + @params end - @params end end diff --git a/lib/inspec/resources/iis_app_pool.rb b/lib/inspec/resources/iis_app_pool.rb index 80930770d..64fef7a71 100644 --- a/lib/inspec/resources/iis_app_pool.rb +++ b/lib/inspec/resources/iis_app_pool.rb @@ -5,125 +5,127 @@ require "inspec/resources/powershell" # check for web applications in IIS # Note: this is only supported in windows 2012 and later -class IisAppPool < Inspec.resource(1) - name "iis_app_pool" - desc "Tests IIS application pool configuration on windows." - supports platform: "windows" - example <<~EXAMPLE - describe iis_app_pool('DefaultAppPool') do - it { should exist } - its('enable32bit') { should cmp 'True' } - its('runtime_version') { should eq 'v4.0' } - its('pipeline_mode') { should eq 'Integrated' } +module Inspec::Resources + class IisAppPool < Inspec.resource(1) + name "iis_app_pool" + desc "Tests IIS application pool configuration on windows." + supports platform: "windows" + example <<~EXAMPLE + describe iis_app_pool('DefaultAppPool') do + it { should exist } + its('enable32bit') { should cmp 'True' } + its('runtime_version') { should eq 'v4.0' } + its('pipeline_mode') { should eq 'Integrated' } + end + EXAMPLE + + def initialize(pool_name) + @pool_name = pool_name + @pool_path = "IIS:\\AppPools\\#{@pool_name}" + @cache = nil + + # verify that this resource is only supported on Windows + return skip_resource "The `iis_app_pool` resource is not supported on your OS." unless inspec.os.windows? end - EXAMPLE - def initialize(pool_name) - @pool_name = pool_name - @pool_path = "IIS:\\AppPools\\#{@pool_name}" - @cache = nil + def pool_name + iis_app_pool[:pool_name] + end - # verify that this resource is only supported on Windows - return skip_resource "The `iis_app_pool` resource is not supported on your OS." unless inspec.os.windows? - end + def runtime_version + iis_app_pool[:version] + end - def pool_name - iis_app_pool[:pool_name] - end + def enable32bit + iis_app_pool[:e32b] + end - def runtime_version - iis_app_pool[:version] - end + def pipeline_mode + iis_app_pool[:mode] + end - def enable32bit - iis_app_pool[:e32b] - end + def max_processes + iis_app_pool[:processes] + end - def pipeline_mode - iis_app_pool[:mode] - end + def timeout + iis_app_pool[:timeout] + end - def max_processes - iis_app_pool[:processes] - end + def timeout_days + iis_app_pool[:timeout_days] + end - def timeout - iis_app_pool[:timeout] - end + def timeout_hours + iis_app_pool[:timeout_hours] + end - def timeout_days - iis_app_pool[:timeout_days] - end + def timeout_minutes + iis_app_pool[:timeout_minutes] + end - def timeout_hours - iis_app_pool[:timeout_hours] - end + def timeout_seconds + iis_app_pool[:timeout_seconds] + end - def timeout_minutes - iis_app_pool[:timeout_minutes] - end + def user_identity_type + iis_app_pool[:user_identity_type] + end - def timeout_seconds - iis_app_pool[:timeout_seconds] - end + def username + iis_app_pool[:username] + end - def user_identity_type - iis_app_pool[:user_identity_type] - end + def exists? + !iis_app_pool[:pool_name].empty? + end - def username - iis_app_pool[:username] - end + def to_s + "IIS App Pool '#{@pool_name}'" + end - def exists? - !iis_app_pool[:pool_name].empty? - end + private - def to_s - "IIS App Pool '#{@pool_name}'" - end + def iis_app_pool + return @cache unless @cache.nil? - private + # We use `-Compress` here to avoid a bug in PowerShell + # It does not affect validity of the output, only the representation + # See: https://github.com/inspec/inspec/pull/3842 + script = <<~EOH + Import-Module WebAdministration + If (Test-Path '#{@pool_path}') { + Get-Item '#{@pool_path}' | Select-Object * | ConvertTo-Json -Compress + } Else { + Write-Host '{}' + } + EOH + cmd = inspec.powershell(script) - def iis_app_pool - return @cache unless @cache.nil? + begin + pool = JSON.parse(cmd.stdout) + rescue JSON::ParserError => _e + raise Inspec::Exceptions::ResourceFailed, "Unable to parse app pool JSON" + end - # We use `-Compress` here to avoid a bug in PowerShell - # It does not affect validity of the output, only the representation - # See: https://github.com/inspec/inspec/pull/3842 - script = <<~EOH - Import-Module WebAdministration - If (Test-Path '#{@pool_path}') { - Get-Item '#{@pool_path}' | Select-Object * | ConvertTo-Json -Compress - } Else { - Write-Host '{}' + process_model = pool.fetch("processModel", {}) + idle_timeout = process_model.fetch("idleTimeout", {}) + + # map our values to a hash table + @cache = { + pool_name: pool["name"], + version: pool["managedRuntimeVersion"], + e32b: pool["enable32BitAppOnWin64"], + mode: pool["managedPipelineMode"], + processes: process_model["maxProcesses"], + timeout: "#{idle_timeout["Hours"]}:#{idle_timeout["Minutes"]}:#{idle_timeout["Seconds"]}", + timeout_days: idle_timeout["Days"], + timeout_hours: idle_timeout["Hours"], + timeout_minutes: idle_timeout["Minutes"], + timeout_seconds: idle_timeout["Seconds"], + user_identity_type: process_model["identityType"], + username: process_model["userName"], } - EOH - cmd = inspec.powershell(script) - - begin - pool = JSON.parse(cmd.stdout) - rescue JSON::ParserError => _e - raise Inspec::Exceptions::ResourceFailed, "Unable to parse app pool JSON" end - - process_model = pool.fetch("processModel", {}) - idle_timeout = process_model.fetch("idleTimeout", {}) - - # map our values to a hash table - @cache = { - pool_name: pool["name"], - version: pool["managedRuntimeVersion"], - e32b: pool["enable32BitAppOnWin64"], - mode: pool["managedPipelineMode"], - processes: process_model["maxProcesses"], - timeout: "#{idle_timeout["Hours"]}:#{idle_timeout["Minutes"]}:#{idle_timeout["Seconds"]}", - timeout_days: idle_timeout["Days"], - timeout_hours: idle_timeout["Hours"], - timeout_minutes: idle_timeout["Minutes"], - timeout_seconds: idle_timeout["Seconds"], - user_identity_type: process_model["identityType"], - username: process_model["userName"], - } end end diff --git a/lib/inspec/resources/ssl.rb b/lib/inspec/resources/ssl.rb index 4ec909d3c..21907d0b2 100644 --- a/lib/inspec/resources/ssl.rb +++ b/lib/inspec/resources/ssl.rb @@ -6,92 +6,94 @@ require "uri" require "parallel" # Custom resource based on the InSpec resource DSL -class SSL < Inspec.resource(1) - name "ssl" - supports platform: "unix" - supports platform: "windows" +module Inspec::Resources + class SSL < Inspec.resource(1) + name "ssl" + supports platform: "unix" + supports platform: "windows" - desc " - SSL test resource - " + desc " + SSL test resource + " - example <<~EXAMPLE - describe ssl(port: 443) do - it { should be_enabled } - end - - # protocols: ssl2, ssl3, tls1.0, tls1.1, tls1.2 - describe ssl(port: 443).protocols('ssl2') do - it { should_not be_enabled } - end - - # any ciphers, filter by name or regex - describe ssl(port: 443).ciphers(/rc4/i) do - it { should_not be_enabled } - end - EXAMPLE - - VERSIONS = [ - "ssl2", - "ssl3", - "tls1.0", - "tls1.1", - "tls1.2", - ].freeze - - attr_reader :host, :port, :timeout, :retries - - def initialize(opts = {}) - @host = opts[:host] - if @host.nil? - # Transports like SSH and WinRM will provide a hostname - if inspec.backend.respond_to?("hostname") - @host = inspec.backend.hostname - elsif inspec.backend.class.to_s == "Train::Transports::Local::Connection" - @host = "localhost" + example <<~EXAMPLE + describe ssl(port: 443) do + it { should be_enabled } end - end - @port = opts[:port] || 443 - @timeout = opts[:timeout] - @retries = opts[:retries] - end - filter = FilterTable.create - filter.register_custom_matcher(:enabled?) do |x| - raise "Cannot determine host for SSL test. Please specify it or use a different target." if x.resource.host.nil? - - x.handshake.values.any? { |i| i["success"] } - end - filter.register_column(:ciphers, field: "cipher") - .register_column(:protocols, field: "protocol") - .register_custom_property(:handshake) do |x| - groups = x.entries.group_by(&:protocol) - res = Parallel.map(groups, in_threads: 8) do |proto, e| - [proto, SSLShake.hello(x.resource.host, port: x.resource.port, - protocol: proto, ciphers: e.map(&:cipher), - timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)] + # protocols: ssl2, ssl3, tls1.0, tls1.1, tls1.2 + describe ssl(port: 443).protocols('ssl2') do + it { should_not be_enabled } end - Hash[res] - end - .install_filter_methods_on_resource(self, :scan_config) - def to_s - "SSL/TLS on #{@host}:#{@port}" - end - - private - - def scan_config - [ - { "protocol" => "ssl2", "ciphers" => SSLShake::SSLv2::CIPHERS.keys }, - { "protocol" => "ssl3", "ciphers" => SSLShake::TLS::SSL3_CIPHERS.keys }, - { "protocol" => "tls1.0", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys }, - { "protocol" => "tls1.1", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys }, - { "protocol" => "tls1.2", "ciphers" => SSLShake::TLS::TLS_CIPHERS.keys }, - ].map do |line| - line["ciphers"].map do |cipher| - { "protocol" => line["protocol"], "cipher" => cipher } + # any ciphers, filter by name or regex + describe ssl(port: 443).ciphers(/rc4/i) do + it { should_not be_enabled } end - end.flatten + EXAMPLE + + VERSIONS = [ + "ssl2", + "ssl3", + "tls1.0", + "tls1.1", + "tls1.2", + ].freeze + + attr_reader :host, :port, :timeout, :retries + + def initialize(opts = {}) + @host = opts[:host] + if @host.nil? + # Transports like SSH and WinRM will provide a hostname + if inspec.backend.respond_to?("hostname") + @host = inspec.backend.hostname + elsif inspec.backend.class.to_s == "Train::Transports::Local::Connection" + @host = "localhost" + end + end + @port = opts[:port] || 443 + @timeout = opts[:timeout] + @retries = opts[:retries] + end + + filter = FilterTable.create + filter.register_custom_matcher(:enabled?) do |x| + raise "Cannot determine host for SSL test. Please specify it or use a different target." if x.resource.host.nil? + + x.handshake.values.any? { |i| i["success"] } + end + filter.register_column(:ciphers, field: "cipher") + .register_column(:protocols, field: "protocol") + .register_custom_property(:handshake) do |x| + groups = x.entries.group_by(&:protocol) + res = Parallel.map(groups, in_threads: 8) do |proto, e| + [proto, SSLShake.hello(x.resource.host, port: x.resource.port, + protocol: proto, ciphers: e.map(&:cipher), + timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)] + end + Hash[res] + end + .install_filter_methods_on_resource(self, :scan_config) + + def to_s + "SSL/TLS on #{@host}:#{@port}" + end + + private + + def scan_config + [ + { "protocol" => "ssl2", "ciphers" => SSLShake::SSLv2::CIPHERS.keys }, + { "protocol" => "ssl3", "ciphers" => SSLShake::TLS::SSL3_CIPHERS.keys }, + { "protocol" => "tls1.0", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys }, + { "protocol" => "tls1.1", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys }, + { "protocol" => "tls1.2", "ciphers" => SSLShake::TLS::TLS_CIPHERS.keys }, + ].map do |line| + line["ciphers"].map do |cipher| + { "protocol" => line["protocol"], "cipher" => cipher } + end + end.flatten + end end end