From 10dc5621fb9d0626a60d4874622337f42aeac187 Mon Sep 17 00:00:00 2001 From: Jared Quick Date: Tue, 2 Jan 2018 14:04:13 -0500 Subject: [PATCH] Add platform resource and platform supports (#2393) * Add platform resource and platform supports. Signed-off-by: Jared Quick * Cache platform and inspec checks and implement inspec_version. Signed-off-by: Jared Quick * Deprecate current inspec support in favor of inspec_version. Signed-off-by: Jared Quick * Update resource/profile skip messages. Signed-off-by: Jared Quick * Update load_resource to use platform instead of os. Signed-off-by: Jared Quick * Update platform example. Signed-off-by: Jared Quick --- lib/inspec/control_eval_context.rb | 13 ++- lib/inspec/metadata.rb | 55 +++-------- lib/inspec/plugins/resource.rb | 20 +++- lib/inspec/profile.rb | 14 ++- lib/inspec/profile_context.rb | 10 +- lib/inspec/resource.rb | 5 + lib/inspec/runner.rb | 4 +- lib/resources/os.rb | 28 ++---- lib/resources/platform.rb | 92 ++++++++++++++++++ .../mock/profiles/supported_inspec/inspec.yml | 3 +- .../profiles/unsupported_inspec/inspec.yml | 3 +- test/unit/plugins/resource_test.rb | 31 ++++++ test/unit/profiles/metadata_test.rb | 75 ++++++++++---- test/unit/resources/platform_test.rb | 97 +++++++++++++++++++ 14 files changed, 355 insertions(+), 95 deletions(-) create mode 100644 lib/resources/platform.rb create mode 100644 test/unit/resources/platform_test.rb diff --git a/lib/inspec/control_eval_context.rb b/lib/inspec/control_eval_context.rb index e696627ff..0b87bcccc 100644 --- a/lib/inspec/control_eval_context.rb +++ b/lib/inspec/control_eval_context.rb @@ -117,10 +117,21 @@ module Inspec end define_method :register_control do |control, &block| - if @skip_file || !profile_context_owner.profile_supports_os? + if @skip_file ::Inspec::Rule.set_skip_rule(control, true) end + unless profile_context_owner.profile_supports_platform? + platform = inspec.platform + msg = "Profile #{profile_context_owner.profile_id} is not supported on platform #{platform.name}/#{platform.release}." + ::Inspec::Rule.set_skip_rule(control, msg) + end + + unless profile_context_owner.profile_supports_inspec_version? + msg = "Profile #{profile_context_owner.profile_id} is not supported on InSpec version (#{Inspec::VERSION})." + ::Inspec::Rule.set_skip_rule(control, msg) + end + profile_context_owner.register_rule(control, &block) unless control.nil? end diff --git a/lib/inspec/metadata.rb b/lib/inspec/metadata.rb index ff663b2ac..c961b616d 100644 --- a/lib/inspec/metadata.rb +++ b/lib/inspec/metadata.rb @@ -36,6 +36,7 @@ module Inspec summary description version + inspec_version }.each do |name| define_method name.to_sym do |arg| params[name.to_sym] = arg @@ -52,38 +53,16 @@ module Inspec # already. end - def is_supported?(os, entry) - name = entry[:'os-name'] || entry[:os] - family = entry[:'os-family'] - release = entry[:release] - - # return true if the backend matches the supported OS's - # fields act as masks, i.e. any value configured for os-name, os-family, - # or release must be met by the backend; any field that is nil acts as - # a glob expression i.e. is true - - # os name is both saved in :family and :name, so check both - name_ok = name.nil? || - os[:name] == name || os[:family] == name - - family_check = family.to_s + '?' - family_ok = family.nil? || os[:family] == family || - ( - os.respond_to?(family_check) && - # this call will return true if the family matches - os.method(family_check).call - ) - - # ensure we do have a string if we have a non-nil value eg. 16.06 - release_ok = release.nil? || os[:release] == release - - # we want to make sure that all matchers are true - name_ok && family_ok && release_ok - end - def inspec_requirement - inspec = params[:supports].find { |x| !x[:inspec].nil? } || {} - Gem::Requirement.create(inspec[:inspec]) + inspec_in_supports = params[:supports].find { |x| !x[:inspec].nil? } + if inspec_in_supports + Inspec::Log.warn '[DEPRECATED] The use of inspec.yml `supports:inspec` is deprecated and will be removed in InSpec 2.0. Please use `inspec_version` instead.' + Gem::Requirement.create(inspec_in_supports[:inspec]) + else + # using Gem::Requirement here to allow nil values which + # translate to [">= 0"] + Gem::Requirement.create(params[:inspec_version]) + end end def supports_runtime? @@ -91,18 +70,8 @@ module Inspec inspec_requirement.satisfied_by?(running) end - def supports_transport?(backend) - # with no supports specified, always return true, as there are no - # constraints on the supported backend; it is equivalent to putting - # all fields into accept-all mode - return true if params[:supports].empty? - - found = params[:supports].find do |entry| - is_supported?(backend.os, entry) - end - - # finally, if we found a supported entry, we are good to go - !found.nil? + def supports_platform?(backend) + backend.platform.supported?(params[:supports]) end # return all warn and errors diff --git a/lib/inspec/plugins/resource.rb b/lib/inspec/plugins/resource.rb index fbae4315f..b21cbe5f7 100644 --- a/lib/inspec/plugins/resource.rb +++ b/lib/inspec/plugins/resource.rb @@ -28,6 +28,12 @@ module Inspec __resource_registry[@name].desc(description) end + def supports(criteria = nil) + return if criteria.nil? + Inspec::Resource.supports[@name] ||= [] + Inspec::Resource.supports[@name].push(criteria) + end + def example(example = nil) return if example.nil? __resource_registry[@name].example(example) @@ -37,18 +43,22 @@ module Inspec Inspec::Resource.registry end - def __register(name, obj) # rubocop:disable Metrics/MethodLength - cl = Class.new(obj) do + def __register(name, obj) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + cl = Class.new(obj) do # rubocop:disable Metrics/BlockLength attr_reader :resource_exception_message def initialize(backend, name, *args) @resource_skipped = false @resource_failed = false + @supports = Inspec::Resource.supports[name] # attach the backend to this instance @__backend_runner__ = backend @__resource_name__ = name + # check resource supports + check_supports unless @supports.nil? + # call the resource initializer begin super(*args) @@ -69,6 +79,12 @@ module Inspec @example = example end + def check_supports + status = inspec.platform.supported?(@supports) + skip_msg = "Resource #{@__resource_name__.capitalize} is not supported on platform #{inspec.platform.name}/#{inspec.platform.release}." + skip_resource(skip_msg) unless status + end + def skip_resource(message) @resource_skipped = true @resource_exception_message = message diff --git a/lib/inspec/profile.rb b/lib/inspec/profile.rb index 0247e1be7..3b5ba8fcb 100644 --- a/lib/inspec/profile.rb +++ b/lib/inspec/profile.rb @@ -136,15 +136,21 @@ module Inspec # @returns [TrueClass, FalseClass] # def supported? - supports_os? && supports_runtime? + supports_platform? && supports_runtime? end - def supports_os? - metadata.supports_transport?(@backend) + def supports_platform? + if @supports_platform.nil? + @supports_platform = metadata.supports_platform?(@backend) + end + @supports_platform end def supports_runtime? - metadata.supports_runtime? + if @supports_runtime.nil? + @supports_runtime = metadata.supports_runtime? + end + @supports_runtime end def params diff --git a/lib/inspec/profile_context.rb b/lib/inspec/profile_context.rb index 26d4f59d8..2bbc1a806 100644 --- a/lib/inspec/profile_context.rb +++ b/lib/inspec/profile_context.rb @@ -63,10 +63,16 @@ module Inspec @control_eval_context = nil end - def profile_supports_os? + def profile_supports_platform? return true if @conf['profile'].nil? - @conf['profile'].supports_os? + @conf['profile'].supports_platform? + end + + def profile_supports_inspec_version? + return true if @conf['profile'].nil? + + @conf['profile'].supports_runtime? end def remove_rule(id) diff --git a/lib/inspec/resource.rb b/lib/inspec/resource.rb index b866385b5..242c99b9c 100644 --- a/lib/inspec/resource.rb +++ b/lib/inspec/resource.rb @@ -16,6 +16,10 @@ module Inspec @registry ||= default_registry end + def self.supports + @supports ||= {} + end + def self.new_registry default_registry.dup end @@ -131,6 +135,7 @@ require 'resources/packages' require 'resources/parse_config' require 'resources/passwd' require 'resources/pip' +require 'resources/platform' require 'resources/port' require 'resources/postgres' require 'resources/postgres_conf' diff --git a/lib/inspec/runner.rb b/lib/inspec/runner.rb index 4680ee896..465ffb889 100644 --- a/lib/inspec/runner.rb +++ b/lib/inspec/runner.rb @@ -185,8 +185,8 @@ module Inspec "InSpec v#{Inspec::VERSION}.\n" end - if !profile.supports_os? - raise "This OS/platform (#{@backend.os[:name]}) is not supported by this profile." + if !profile.supports_platform? + raise "This OS/platform (#{@backend.platform.name}/#{@backend.platform.release}) is not supported by this profile." end true diff --git a/lib/resources/os.rb b/lib/resources/os.rb index 26ac6faf4..68d2d021d 100644 --- a/lib/resources/os.rb +++ b/lib/resources/os.rb @@ -2,8 +2,10 @@ # author: Dominik Richter # author: Christoph Hartmann +require 'resources/platform' + module Inspec::Resources - class OSResource < Inspec.resource(1) + class OSResource < PlatformResource name 'os' desc 'Use the os InSpec audit resource to test the platform on which the system is running.' example " @@ -23,31 +25,17 @@ module Inspec::Resources # reuse helper methods from backend %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux? darwin?}.each do |os_family| define_method(os_family.to_sym) do - inspec.backend.os.send(os_family) - end - end - - def [](name) - # convert string to symbol - name = name.to_sym if name.is_a? String - inspec.backend.os[name] - end - - # add helper methods for easy access of properties - # allows users to use os.name, os.family, os.release, os.arch - %w{name family release arch}.each do |property| - define_method(property.to_sym) do - inspec.backend.os[property.to_sym] + @platform.send(os_family) end end # helper to collect a hash object easily def params { - name: inspec.backend.os[:name], - family: inspec.backend.os[:family], - release: inspec.backend.os[:release], - arch: inspec.backend.os[:arch], + name: @platform[:name], + family: @platform[:family], + release: @platform[:release], + arch: @platform[:arch], } end diff --git a/lib/resources/platform.rb b/lib/resources/platform.rb new file mode 100644 index 000000000..bb41b42f8 --- /dev/null +++ b/lib/resources/platform.rb @@ -0,0 +1,92 @@ +# encoding: utf-8 + +module Inspec::Resources + class PlatformResource < Inspec.resource(1) + name 'platform' + desc 'Use the platform InSpec resource to test the platform on which the system is running.' + example " + describe platform do + its('name') { should eq 'redhat' } + end + + describe platform do + it { should be_in_family('unix') } + end + " + + def initialize + @platform = inspec.backend.os + end + + # add helper methods for easy access of properties + %w{name family release arch}.each do |property| + define_method(property.to_sym) do + @platform.send(property) + end + end + + def [](name) + # convert string to symbol + name = name.to_sym if name.is_a? String + @platform[name] + end + + def platform?(name) + @platform.name == name || + @platform.family_hierarchy.include?(name) + end + + def in_family?(family) + @platform.family_hierarchy.include?(family) + end + + def families + @platform.family_hierarchy + end + + def supported?(supports) + return true if supports.nil? || supports.empty? + + status = true + supports.each do |s| + s.each do |k, v| + # ignore the inspec check for supports + # TODO: remove in inspec 2.0 + if k == :inspec + next + elsif %i(os_family os-family platform_family platform-family).include?(k) + status = in_family?(v) + elsif %i(os platform).include?(k) + status = platform?(v) + elsif %i(os_name os-name platform_name platform-name).include?(k) + status = name == v + elsif k == :release + status = check_release(v) + else + status = false + end + break if status == false + end + return true if status == true + end + + status + end + + def to_s + 'Platform Detection' + end + + private + + def check_release(value) + # allow wild card matching + if value.include?('*') + cleaned = Regexp.escape(value).gsub('\*', '.*?') + !(release =~ /#{cleaned}/).nil? + else + release == value + end + end + end +end diff --git a/test/unit/mock/profiles/supported_inspec/inspec.yml b/test/unit/mock/profiles/supported_inspec/inspec.yml index 298dbb846..ac8917153 100644 --- a/test/unit/mock/profiles/supported_inspec/inspec.yml +++ b/test/unit/mock/profiles/supported_inspec/inspec.yml @@ -1,2 +1 @@ -supports: -- inspec: 0.18 +inspec_version: 0.18 diff --git a/test/unit/mock/profiles/unsupported_inspec/inspec.yml b/test/unit/mock/profiles/unsupported_inspec/inspec.yml index cfce12b56..2851c9eb8 100644 --- a/test/unit/mock/profiles/unsupported_inspec/inspec.yml +++ b/test/unit/mock/profiles/unsupported_inspec/inspec.yml @@ -1,2 +1 @@ -supports: -- inspec: '>= 99.0.0' +inspec_version: '>= 99.0.0' diff --git a/test/unit/plugins/resource_test.rb b/test/unit/plugins/resource_test.rb index a1e2decd1..b0f8f26e2 100644 --- a/test/unit/plugins/resource_test.rb +++ b/test/unit/plugins/resource_test.rb @@ -51,4 +51,35 @@ describe Inspec::Plugins::Resource do end end + describe 'supported platform' do + def supports_meta(supports) + Inspec::Resource.supports['os'] = supports + load_resource('os') + end + + it 'loads a profile which supports multiple families' do + m = supports_meta([ + { os_family: 'windows' }, + { os_family: 'unix' } + ]) + m.check_supports.must_be_nil + end + + it 'loads a profile which supports multiple names' do + m = supports_meta([ + { os_family: 'windows', os_name: 'windows_2000'}, + { os_family: 'unix', os_name: 'ubuntu' } + ]) + m.check_supports.must_be_nil + end + + it 'reject a profile which supports multiple families' do + m = supports_meta([ + { os_family: 'windows' }, + { os_family: 'redhat' } + ]) + expect = 'Resource Os is not supported on platform ubuntu/14.04.' + m.check_supports.must_equal expect + end + end end diff --git a/test/unit/profiles/metadata_test.rb b/test/unit/profiles/metadata_test.rb index 24c324f27..d2124887c 100644 --- a/test/unit/profiles/metadata_test.rb +++ b/test/unit/profiles/metadata_test.rb @@ -101,14 +101,14 @@ describe 'metadata with supported operating systems' do it 'load a profile with empty supports clause' do m = supports_meta(nil) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'supports legacy simple support style, but warns' do # i.e. setting this to something that would fail: logger.expect :warn, nil, ["Do not use deprecated `supports: linux` syntax. Instead use:\nsupports:\n - os-family: linux\n\n"] m = supports_meta('linux') - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true logger.verify end @@ -116,53 +116,87 @@ describe 'metadata with supported operating systems' do # i.e. setting this to something that would fail: logger.expect :warn, nil, ["Do not use deprecated `supports: linux` syntax. Instead use:\nsupports:\n - os-family: linux\n\n"] m = supports_meta(['linux']) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true logger.verify end it 'loads a profile which supports os ubuntu' do m = supports_meta({ 'os' => 'ubuntu' }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'loads a profile which supports os name ubuntu' do m = supports_meta({ 'os-name' => 'ubuntu' }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'loads a profile which supports os family linux' do m = supports_meta({ 'os-family' => 'linux' }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'loads a profile which supports release 14.04' do m = supports_meta({ 'release' => '14.04' }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'rejects a profile which supports release 12.04' do m = supports_meta({ 'release' => '12.04' }) - m.supports_transport?(backend).must_equal false + m.supports_platform?(backend).must_equal false end it 'loads a profile which supports ubuntu 14.04' do m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '14.04' }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true + end + + it 'loads a profile which supports ubuntu 14.*' do + m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '14.*' }) + m.supports_platform?(backend).must_equal true end it 'rejects a profile which supports ubuntu 12.04' do m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '12.04' }) - m.supports_transport?(backend).must_equal false + m.supports_platform?(backend).must_equal false + end + + it 'rejects a profile which supports ubuntu 12.*' do + m = supports_meta({ 'os-name' => 'ubuntu', 'release' => '12.*' }) + m.supports_platform?(backend).must_equal false end it 'loads a profile which supports ubuntu float 14.04 as parsed by yml' do m = supports_meta({ 'os-name' => 'ubuntu', 'release' => 14.04 }) - m.supports_transport?(backend).must_equal true + m.supports_platform?(backend).must_equal true end it 'reject unsupported os' do m = supports_meta({ 'os-name' => 'windows' }) - m.supports_transport?(backend).must_equal false + m.supports_platform?(backend).must_equal false + end + + it 'loads a profile which supports multiple families' do + m = supports_meta([ + { 'os-family' => 'windows' }, + { 'os-family' => 'unix' } + ]) + m.supports_platform?(backend).must_equal true + end + + it 'loads a profile which supports multiple names' do + m = supports_meta([ + { 'os-family' => 'windows', 'os-name' => 'windows_2000'}, + { 'os-family' => 'unix', 'os-name' => 'ubuntu' } + ]) + m.supports_platform?(backend).must_equal true + end + + it 'reject a profile which supports multiple families' do + m = supports_meta([ + { 'os-family' => 'windows' }, + { 'os-family' => 'redhat' } + ]) + m.supports_platform?(backend).must_equal false end end @@ -170,28 +204,35 @@ describe 'metadata with supported operating systems' do let(:current_version) { Inspec::VERSION } let(:next_version) { Gem::Version.new(current_version).bump.to_s } + def version_meta(params) + res = Inspec::Metadata.from_yaml('mock', "---", nil, logger) + res.params[:inspec_version] = params + Inspec::Metadata.finalize(res, 'mock', empty_options, logger) + res + end + it 'returns true on testing the current version' do - m = supports_meta({ 'inspec' => current_version }) + m = version_meta(current_version) m.supports_runtime?.must_equal true end it 'returns true on testing the current version' do - m = supports_meta({ 'inspec' => '= ' + current_version }) + m = version_meta('= ' + current_version) m.supports_runtime?.must_equal true end it 'returns true on testing >= current version' do - m = supports_meta({ 'inspec' => '>= ' + current_version }) + m = version_meta('>= ' + current_version) m.supports_runtime?.must_equal true end it 'returns false on testing >= the next version' do - m = supports_meta({ 'inspec' => '>= ' + next_version }) + m = version_meta('>= ' + next_version) m.supports_runtime?.must_equal false end it 'returns false on testing > the next version' do - m = supports_meta({ 'inspec' => '> ' + next_version }) + m = version_meta('> ' + next_version) m.supports_runtime?.must_equal false end end diff --git a/test/unit/resources/platform_test.rb b/test/unit/resources/platform_test.rb new file mode 100644 index 000000000..f002a5442 --- /dev/null +++ b/test/unit/resources/platform_test.rb @@ -0,0 +1,97 @@ +# encoding: utf-8 + +require 'helper' +require 'inspec/resource' + +describe 'Inspec::Resources::Platform' do + let(:resource) { resource = MockLoader.new(:ubuntu1504).load_resource('platform') } + + it 'verify platform parsing on Ubuntu' do + _(resource.name).must_equal 'ubuntu' + _(resource.family).must_equal 'debian' + _(resource.release).must_equal '15.04' + _(resource.arch).must_equal 'x86_64' + end + + it 'verify platform hash parsing on Ubuntu' do + _(resource[:name]).must_equal 'ubuntu' + _(resource[:family]).must_equal 'debian' + _(resource[:release]).must_equal '15.04' + _(resource[:arch]).must_equal 'x86_64' + end + + it 'verify platform families' do + expect = ["debian", "linux", "unix"] + _(resource.families).must_equal expect + end + + it 'verify platform? responds correctly' do + _(resource.platform?('windows')).must_equal false + _(resource.platform?('unix')).must_equal true + _(resource.platform?('ubuntu')).must_equal true + _(resource.platform?('mac_os_x')).must_equal false + end + + it 'verify family? responds correctly' do + _(resource.in_family?('windows')).must_equal false + _(resource.in_family?('unix')).must_equal true + _(resource.in_family?('ubuntu')).must_equal false + _(resource.in_family?('mac_os_x')).must_equal false + end + + it 'verify supported? with multiple families' do + supports = [ + { os_family: 'windows' }, + { os_family: 'unix' } + ] + resource.supported?(supports).must_equal true + end + + it 'loads a profile which supports multiple names' do + supports = [ + { 'os-family': 'windows', 'os-name': 'windows_2000'}, + { 'os-family': 'unix', 'os-name': 'ubuntu' } + ] + resource.supported?(supports).must_equal true + end + + it 'reject a profile which supports multiple families' do + supports = [ + { os_family: 'windows' }, + { os_family: 'redhat' } + ] + resource.supported?(supports).must_equal false + end + + it 'loads a profile which supports release 15.04' do + supports = [ + { 'os-family': 'windows', 'os-name': 'windows_2000'}, + { 'os-name': 'ubuntu', 'release': '15.04'} + ] + resource.supported?(supports).must_equal true + end + + it 'loads a profile which supports release 15.*' do + supports = [ + { 'os-family': 'windows', 'os-name': 'windows_2000'}, + { 'os-name': 'ubuntu', 'release': '15.*'} + ] + resource.supported?(supports).must_equal true + end + + it 'loads a profile which supports release *.04' do + supports = [ + { 'os-family': 'windows', 'os-name': 'windows_2000'}, + { 'os-name': 'ubuntu', 'release': '*.04'} + ] + resource.supported?(supports).must_equal true + end + + it 'reject a profile which supports release 12.*' do + supports = [ + { 'os-family': 'windows', 'os-name': 'windows_2000'}, + { 'os-name': 'ubuntu', 'release': '12.*'} + ] + resource.supported?(supports).must_equal false + end +end