inspec 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 70a0c204211c2bd4a689aff2790fe23999a5013a
4
- data.tar.gz: ee648ad90d3d80a6f2cd4f2c18207d39916a6b49
3
+ metadata.gz: 5e3b076ffed41d9148eb98065a025a6acb6a9f70
4
+ data.tar.gz: eb36dc73cf37bc30a46949943ba7db582ed82417
5
5
  SHA512:
6
- metadata.gz: c71d588c6547180d6f54e769c1580dc88d98634cc7100583e6723ea172014fb1f3feb7471791cbd9429b112bda7dbba840564e6ea65ec61faf36427847ca92e2
7
- data.tar.gz: 0d5750125ca6c6cce5ab69a001726dc7663ae95b2269cf8a162ff9e08037ae64ca3607a220af7ae11f47b2c623b3f17b99da92ebabd8650481ae6916b5286a18
6
+ metadata.gz: 09d87d6da242d31586af6d3e39e913223a141d6dbc39474c921636d3169a57bcf1b5029defc1d2f6a8f01c246fe3494846edfe9d00a27381f3aa13f8f335435a
7
+ data.tar.gz: d2806a4c8e637a1ec46cf3ed9ea5a40fc8dd32a6e3bb0de63f405d33f4de9a09d3f25faf36932d949b73c7d9ff1d11949e59963f6ab6116402890632facb0227
data/.rubocop.yml CHANGED
@@ -6,6 +6,7 @@ AllCops:
6
6
  - 'test/**/*'
7
7
  - 'examples/**/*'
8
8
  - 'vendor/**/*'
9
+ - 'lib/bundles/inspec-init/templates/**/*'
9
10
  Documentation:
10
11
  Enabled: false
11
12
  AlignParameters:
data/CHANGELOG.md CHANGED
@@ -1,7 +1,27 @@
1
1
  # Change Log
2
2
 
3
- ## [0.11.0](https://github.com/chef/inspec/tree/0.11.0) (2016-02-09)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v0.10.1...0.11.0)
3
+ ## [0.12.0](https://github.com/chef/inspec/tree/0.12.0) (2016-02-15)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v0.11.0...0.12.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - add runlevel support for System V services [\#455](https://github.com/chef/inspec/pull/455) ([arlimus](https://github.com/arlimus))
9
+ - Add a init subcommand [\#454](https://github.com/chef/inspec/pull/454) ([chris-rock](https://github.com/chris-rock))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Windows 2008 isn't being detected. [\#346](https://github.com/chef/inspec/issues/346)
14
+ - Fix two minor logging and config bugs in CLI [\#452](https://github.com/chef/inspec/pull/452) ([srenatus](https://github.com/srenatus))
15
+ - bugfix: verify the target resolver before using it [\#449](https://github.com/chef/inspec/pull/449) ([arlimus](https://github.com/arlimus))
16
+ - Fix iptables on CentOS6 + more tests for iptables \(plus small code improvements\) [\#442](https://github.com/chef/inspec/pull/442) ([srenatus](https://github.com/srenatus))
17
+
18
+ **Merged pull requests:**
19
+
20
+ - rework target to resolver connection [\#447](https://github.com/chef/inspec/pull/447) ([arlimus](https://github.com/arlimus))
21
+ - separate directory resolver from target resolver [\#446](https://github.com/chef/inspec/pull/446) ([arlimus](https://github.com/arlimus))
22
+
23
+ ## [v0.11.0](https://github.com/chef/inspec/tree/v0.11.0) (2016-02-10)
24
+ [Full Changelog](https://github.com/chef/inspec/compare/v0.10.1...v0.11.0)
5
25
 
6
26
  **Implemented enhancements:**
7
27
 
@@ -11,12 +31,14 @@
11
31
  **Fixed bugs:**
12
32
 
13
33
  - File stats are not always working properly [\#430](https://github.com/chef/inspec/issues/430)
34
+ - Inspec iptables should have\_rule tests not working [\#420](https://github.com/chef/inspec/issues/420)
14
35
  - Integration test for apache config [\#406](https://github.com/chef/inspec/issues/406)
15
36
  - rework auditd\_rules resource [\#312](https://github.com/chef/inspec/issues/312)
16
37
  - resource/auditd\_rules: update rule list format [\#309](https://github.com/chef/inspec/issues/309)
17
38
 
18
39
  **Merged pull requests:**
19
40
 
41
+ - 0.11.0 [\#443](https://github.com/chef/inspec/pull/443) ([arlimus](https://github.com/arlimus))
20
42
  - Fix supermarket cli registration [\#441](https://github.com/chef/inspec/pull/441) ([chris-rock](https://github.com/chris-rock))
21
43
  - update to winrm 1.6.1 command scheme [\#439](https://github.com/chef/inspec/pull/439) ([arlimus](https://github.com/arlimus))
22
44
  - semantics: rename CLI plugins registry -\> commands [\#435](https://github.com/chef/inspec/pull/435) ([arlimus](https://github.com/arlimus))
data/bin/inspec CHANGED
@@ -84,7 +84,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
84
84
  def archive(path)
85
85
  diagnose
86
86
 
87
- o = options.dup
87
+ o = opts.dup
88
88
  o[:logger] = Logger.new(STDOUT)
89
89
  o[:logger].level = get_log_level(o.log_level)
90
90
 
@@ -129,8 +129,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
129
129
  option :format, type: :string, default: Inspec::NoSummaryFormatter, hide: true
130
130
  def shell_func
131
131
  diagnose
132
+ o = opts.dup
133
+ o[:logger] = Logger.new(STDOUT)
134
+ o[:logger].level = get_log_level(o.log_level)
132
135
 
133
- runner = Inspec::Runner.new(opts)
136
+ runner = Inspec::Runner.new(o)
134
137
  Inspec::Shell.new(runner).start
135
138
  rescue RuntimeError => e
136
139
  puts e.message
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ libdir = File.dirname(__FILE__)
6
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
7
+
8
+ require 'inspec-init/cli'
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+
4
+ module Init
5
+ class CLI < Inspec::BaseCLI
6
+ namespace 'init'
7
+
8
+ # read template directoy
9
+ template_dir = File.join(File.dirname(__FILE__), 'templates')
10
+ Dir.glob(File.join(template_dir, '*')) do |template|
11
+ relative = Pathname.new(template).relative_path_from(Pathname.new(template_dir))
12
+
13
+ # register command for the template
14
+ desc "#{relative} NAME", "Create a new #{relative}"
15
+ option :overwrite, type: :boolean, default: false,
16
+ desc: 'Overwrites existing directory'
17
+ define_method relative.to_s.to_sym do |name|
18
+ generator(relative.to_s, { name: name }, options)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # 1. iterate over all files
25
+ # 2. read content in erb
26
+ # 3. write to target
27
+ def generator(type, attributes = {}, options = {}) # rubocop:disable Metrics/AbcSize
28
+ # path of this script
29
+ dir = File.dirname(__FILE__)
30
+ # look for template directory
31
+ base_dir = File.join(dir, 'templates', type)
32
+ # prepare glob for all subdirectories and files
33
+ template = File.join(base_dir, '**', '{*,.*}')
34
+ # generate target path
35
+ target = Pathname.new(Dir.pwd).join(attributes[:name])
36
+ puts "Create new #{type} at #{mark_text(target)}"
37
+
38
+ # check that the directory does not exist
39
+ if File.exist?(target) && !options['overwrite']
40
+ error "#{mark_text(target)} exists already, use --overwrite"
41
+ exit 1
42
+ end
43
+
44
+ # ensure that target directory is available
45
+ FileUtils.mkdir_p(target)
46
+
47
+ # iterate over files and write to target path
48
+ Dir.glob(template) do |file|
49
+ relative = Pathname.new(file).relative_path_from(Pathname.new(base_dir))
50
+ destination = Pathname.new(target).join(relative)
51
+ if File.directory?(file)
52
+ li "Create directory #{mark_text(relative)}"
53
+ FileUtils.mkdir_p(destination)
54
+ elsif File.file?(file)
55
+ li "Create file #{mark_text(relative)}"
56
+ # read & render content
57
+ content = render(File.read(file), attributes)
58
+ # write file content
59
+ File.write(destination, content)
60
+ else
61
+ puts "Ignore #{file}, because its not an file or directoy"
62
+ end
63
+ end
64
+ end
65
+
66
+ # This is a render helper to bind hash values to a ERB template
67
+ def render(content, hash)
68
+ # create a new binding class
69
+ cls = Class.new do
70
+ hash.each do |key, value|
71
+ define_method key.to_sym do
72
+ value
73
+ end
74
+ end
75
+ # expose binding
76
+ define_method :bind do
77
+ binding
78
+ end
79
+ end
80
+ ERB.new(content).result(cls.new.bind)
81
+ end
82
+ end
83
+
84
+ # register the subcommand to Inspec CLI registry
85
+ Inspec::Plugins::CLI.add_subcommand(Init::CLI, 'init', 'init TEMPLATE ...', 'Scaffolds a new project', {})
86
+ end
@@ -0,0 +1,3 @@
1
+ # Example InSpec Profile
2
+
3
+ This example shows the implementation of an InSpec [profile](../../docs/profiles.rst).
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, The Authors
3
+ # license: All rights reserved
4
+
5
+ title 'sample section'
6
+
7
+ # you can also use plain tests
8
+ describe file('/tmp') do
9
+ it { should be_directory }
10
+ end
11
+
12
+ # you add controls here
13
+ control 'tmp-1.0' do # A unique ID for this control
14
+ impact 0.7 # The criticality, if this control fails.
15
+ title 'Create /tmp directory' # A human-readable title
16
+ desc 'An optional description...'
17
+ describe file('/tmp') do # The actual test
18
+ it { should be_directory }
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ name: <%= name %>
2
+ title: InSpec Profile
3
+ maintainer: The Authors
4
+ copyright: The Authors
5
+ copyright_email: you@example.com
6
+ license: All Rights Reserved
7
+ summary: An InSpec Compliance Profile
8
+ version: 0.1.0
@@ -4,40 +4,30 @@
4
4
 
5
5
  require 'rubygems/package'
6
6
  require 'zlib'
7
+ require 'inspec/targets/dir'
7
8
 
8
9
  module Inspec::Targets
9
- class ArchiveHelper
10
- def resolve(target, _opts = {})
11
- files, rootdir = structure(target)
10
+ class ArchiveHelper < DirsResolver
11
+ attr_reader :files
12
+
13
+ def initialize(target, _opts = {})
14
+ @target = target
15
+ files, @rootdir = structure(target)
12
16
 
13
17
  # remove trailing slashes
14
18
  files = files.collect { |f| f.chomp('/') }
15
19
 
16
20
  # remove leading directory
17
- files = files.collect { |f|
18
- Pathname(f).relative_path_from(Pathname(rootdir)).to_s
21
+ root = Pathname(@rootdir)
22
+ @files = files.collect { |f|
23
+ Pathname(f).relative_path_from(root).to_s
19
24
  }
20
-
21
- helper = DirsHelper.get_handler(files)
22
- if helper.nil?
23
- fail "Don't know how to handle folder #{target}"
24
- end
25
-
26
- res = {
27
- test: collect(helper, files, :get_filenames),
28
- library: collect(helper, files, :get_libraries),
29
- metadata: collect(helper, files, :get_metadata),
30
- }.map { |as, list|
31
- content(target, list, rootdir, base_folder: target, as: as)
32
- }
33
-
34
- res.flatten
35
25
  end
36
26
 
37
- # FIXME(sr) dedup inspec/targets/folder
38
- def collect(helper, files, getter)
39
- return [] unless helper.respond_to? getter
40
- helper.method(getter).call(files)
27
+ def resolve(path, opts = {})
28
+ o = (opts || {})
29
+ o[:base_folder] = @target
30
+ content(@target, path, @rootdir, o)
41
31
  end
42
32
  end
43
33
  end
@@ -8,18 +8,47 @@ module Inspec
8
8
  module Targets
9
9
  extend Modulator
10
10
 
11
- def self.__resolve(items)
12
- items.map do |_|
13
- # @TODO
14
- end.flatten
11
+ # Retrieve the resolver for a given target. Resolvers are any type of
12
+ # instance, that can take a target in the #resolve method, and turn
13
+ # it into raw InSpec code. It will actually retrieve all file's
14
+ # contents, including libraries and metadata.
15
+ #
16
+ # @param [any] target any target you are pointing to; typically a string
17
+ # @return [Inspec::Targets::*] the resolver handling this target
18
+ def self.find_resolver(target)
19
+ modules.values.find { |m| m.handles?(target) }
20
+ end
21
+
22
+ # Retrieve the target handler, i.e. the component that is going to handle
23
+ # all aspects of a given target. Unlike the resolver, this provides
24
+ # access to an object, that understands the target and is able to read
25
+ # all contents, while providing additional methods which InSpec itself
26
+ # does not care about (think: plugins).
27
+ #
28
+ # There is a special case, where you might be pointing to a URL containing
29
+ # a ZIP file which has a full InSpec Profile embedded. This will be
30
+ # resolved to a directory helper (Inspec::Targets::DirsResolver). This
31
+ # method will retrieve the right handler for this directory and provide it
32
+ # with full access all contents (i.e. the DirsResolver will be embedded).
33
+ #
34
+ # @param [any] target any target you are pointing to; typically a string
35
+ # @return [Inspec::Targets::*] the handler for this target
36
+ def self.find_handler(target)
37
+ resolver = find_resolver(target)
38
+ return resolver unless resolver.is_a?(Module) &&
39
+ resolver.ancestors.include?(DirsResolver)
40
+ resolver.from_target(target).handler
15
41
  end
16
42
 
43
+ # Turn targets into actionable InSpec code, library-data and metadata.
44
+ #
45
+ # @param [any] targets any targets you are pointing to; typically strings
46
+ # @param [Hash] additional options for loading
47
+ # @return [Array] an array of resolved data, with :content, :ref, :type
17
48
  def self.resolve(targets, opts = {})
18
49
  Array(targets).map do |target|
19
- handler = modules.values.find { |m| m.handles?(target) }
20
- if handler.nil?
21
- fail "Don't know how to handle target: #{target}"
22
- end
50
+ handler = find_resolver(target) ||
51
+ fail("Don't know how to handle target: #{target}")
23
52
  handler.resolve(target, opts)
24
53
  end.flatten
25
54
  end
@@ -3,79 +3,142 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec::Targets
6
+ # DirsHelper manages resolvers for directories. Wherever directories are,
7
+ # either a local folder, archive, or remote, this module provides all helpers
8
+ # than can resolve these.
9
+ #
10
+ # Resolvers provide these methods
11
+ # * handles?(paths) => true if the resolver is responsible for this target
12
+ # * get_filenames => Array[String] of files where tests/controls are found
13
+ # * get_metadata => String path with metadata information [optional]
14
+ # * get_libraries => Array[String] of library files [optional]
15
+ #
16
+ # Resolvers must register with this DirsHelper via #add_module .
6
17
  module DirsHelper
7
- # InSpec profile Loader
8
- # Previous versions used the `test` directory instead of the new `controls`
9
- # directory. Usage of the test directory is deprecated and not recommended
10
- # anymore. Support for `test` will be removed in InSpec 1.0
11
- # TODO: remove `test` support for InSpec 1.0
12
- class ProfileDir
13
- def handles?(paths)
14
- return true if paths.include?('inspec.yml')
15
- (
16
- !paths.grep(/^controls/).empty? ||
17
- !paths.grep(/^test/).empty?
18
- ) && paths.include?('metadata.rb')
19
- end
18
+ extend Modulator
20
19
 
21
- def get_libraries(paths)
22
- paths.find_all do |path|
23
- path.start_with?('libraries') && path.end_with?('.rb')
24
- end
25
- end
20
+ def self.get_handler(paths)
21
+ modules.values.reverse.find { |x| x.handles?(paths) }
22
+ end
23
+ end
26
24
 
27
- def get_filenames(paths)
28
- paths.find_all do |path|
29
- path.start_with?('controls', 'test') && path.end_with?('.rb')
30
- end
31
- end
25
+ # Base class for all directory resolvers. I.e. any target helper that
26
+ # takes a directory/archive/... and ultimately calls DirsHelper to resolve it.
27
+ #
28
+ # These resolvers must implement the required methods of this class.
29
+ class DirsResolver
30
+ def files
31
+ fail NotImplementedError, "Directory resolver #{self.class} must implement #files"
32
+ end
32
33
 
33
- def get_metadata(paths)
34
- return 'inspec.yml' if paths.include?('inspec.yml')
35
- return 'metadata.rb' if paths.include?('metadata.rb')
36
- end
34
+ def resolve(_path)
35
+ fail NotImplementedError, "Directory resolver #{self.class} must implement #content"
37
36
  end
38
37
 
39
- class ChefAuditDir
40
- def handles?(paths)
41
- paths.include?('recipes') and paths.include?('metadata.rb')
42
- end
38
+ def handler
39
+ DirsHelper.get_handler(files) ||
40
+ fail("Don't know how to handle #{self}")
41
+ end
42
+
43
+ def self.from_target(target)
44
+ new(target)
45
+ end
43
46
 
44
- def get_filenames(paths)
45
- paths.find_all do |x|
46
- x.start_with? 'recipes/' and x.end_with? '.rb'
47
- end
47
+ def self.resolve(target, opts)
48
+ r = new(target, opts)
49
+ handler = r.handler
50
+ files = r.files
51
+
52
+ res = []
53
+ {
54
+ test: __collect(handler, :get_filenames, files),
55
+ library: __collect(handler, :get_libraries, files),
56
+ metadata: __collect(handler, :get_metadata, files),
57
+ }.each do |as, list|
58
+ Array(list).each { |path|
59
+ res.push(r.resolve(path, as: as))
60
+ }
48
61
  end
62
+
63
+ return handler.resolve_contents(res) if handler.respond_to?(:resolve_contents)
64
+ res
65
+ end
66
+
67
+ def self.__collect(handler, getter, files)
68
+ return [] unless handler.respond_to? getter
69
+ handler.method(getter).call(files)
70
+ end
71
+ end
72
+
73
+ # InSpec profile Loader
74
+ # Previous versions used the `test` directory instead of the new `controls`
75
+ # directory. Usage of the test directory is deprecated and not recommended
76
+ # anymore. Support for `test` will be removed in InSpec 1.0
77
+ # TODO: remove `test` support for InSpec 1.0
78
+ class ProfileDir
79
+ def handles?(paths)
80
+ return true if paths.include?('inspec.yml')
81
+ (
82
+ !paths.grep(/^controls/).empty? ||
83
+ !paths.grep(/^test/).empty?
84
+ ) && paths.include?('metadata.rb')
49
85
  end
50
86
 
51
- class ServerspecDir
52
- def handles?(paths)
53
- paths.include?('spec')
87
+ def get_libraries(paths)
88
+ paths.find_all do |path|
89
+ path.start_with?('libraries') && path.end_with?('.rb')
54
90
  end
91
+ end
55
92
 
56
- def get_filenames(paths)
57
- paths.find_all do |path|
58
- path.start_with? 'spec' and path.end_with? '_spec.rb'
59
- end
93
+ def get_filenames(paths)
94
+ paths.find_all do |path|
95
+ path.start_with?('controls', 'test') && path.end_with?('.rb')
60
96
  end
61
97
  end
62
98
 
63
- class FlatDir
64
- def handles?(paths)
65
- get_filenames(paths).empty? == false
99
+ def get_metadata(paths)
100
+ return 'inspec.yml' if paths.include?('inspec.yml')
101
+ return 'metadata.rb' if paths.include?('metadata.rb')
102
+ end
103
+ end
104
+ DirsHelper.add_module('inspec-profile', ProfileDir.new)
105
+
106
+ class ChefAuditDir
107
+ def handles?(paths)
108
+ paths.include?('recipes') and paths.include?('metadata.rb')
109
+ end
110
+
111
+ def get_filenames(paths)
112
+ paths.find_all do |x|
113
+ x.start_with? 'recipes/' and x.end_with? '.rb'
66
114
  end
115
+ end
116
+ end
117
+ DirsHelper.add_module('chef-cookbook', ChefAuditDir.new)
118
+
119
+ class ServerspecDir
120
+ def handles?(paths)
121
+ paths.include?('spec')
122
+ end
67
123
 
68
- def get_filenames(paths)
69
- paths.find_all { |x| x.end_with?('.rb') and !x.include?('/') }
124
+ def get_filenames(paths)
125
+ paths.find_all do |path|
126
+ path.start_with? 'spec' and path.end_with? '_spec.rb'
70
127
  end
71
128
  end
129
+ end
130
+ DirsHelper.add_module('serverspec-folder', ServerspecDir.new)
72
131
 
73
- HANDLERS = [
74
- ProfileDir, ChefAuditDir, ServerspecDir, FlatDir
75
- ].map(&:new)
132
+ class FlatDir
133
+ def handles?(paths)
134
+ get_filenames(paths).empty? == false
135
+ end
76
136
 
77
- def self.get_handler(paths)
78
- HANDLERS.find { |x| x.handles? paths }
137
+ def get_filenames(paths)
138
+ # TODO: eventually remove the metadata.rb exception here
139
+ # when we have fully phased out metadata.rb in 1.0
140
+ paths.find_all { |x| x.end_with?('.rb') && !x.include?('/') && x != 'metadata.rb' }
79
141
  end
80
142
  end
143
+ DirsHelper.add_module('flat-ruby-folder', FlatDir.new)
81
144
  end
@@ -6,48 +6,33 @@ require 'inspec/targets/dir'
6
6
  require 'inspec/targets/file'
7
7
 
8
8
  module Inspec::Targets
9
- class FolderHelper
10
- def handles?(target)
9
+ class FolderHelper < DirsResolver
10
+ def self.handles?(target)
11
11
  File.directory?(target)
12
12
  end
13
13
 
14
- def resolve(target, _opts = {})
14
+ attr_reader :files
15
+
16
+ def initialize(target, _opts = {})
17
+ @base_folder = target
15
18
  # find all files in the folder
16
19
  files = Dir[File.join(target, '**', '*')]
17
20
  # remove the prefix
18
21
  prefix = File.join(target, '')
19
- files = files.map { |x| x.sub(prefix, '') }
20
- # get the dirs helper
21
- helper = DirsHelper.get_handler(files)
22
- if helper.nil?
23
- fail "Don't know how to handle folder #{target}"
24
- end
25
-
26
- # get all test file contents
27
- file_handler = Inspec::Targets.modules['file']
28
- res = {
29
- test: collect(helper, files, :get_filenames),
30
- library: collect(helper, files, :get_libraries),
31
- metadata: collect(helper, files, :get_metadata),
32
- }.map { |as, list|
33
- file_handler.resolve_all(list, base_folder: target, as: as)
34
- }
22
+ @files = files.map { |x| x.sub(prefix, '') }
23
+ @file_handler = Inspec::Targets.modules['file']
24
+ end
35
25
 
36
- # flatten the outer list layer
37
- res.inject(&:+)
26
+ def resolve(path, opts = {})
27
+ o = (opts || {})
28
+ o[:base_folder] = @base_folder
29
+ @file_handler.resolve(path, o)
38
30
  end
39
31
 
40
32
  def to_s
41
33
  'Folder Loader'
42
34
  end
43
-
44
- private
45
-
46
- def collect(helper, files, getter)
47
- return [] unless helper.respond_to? getter
48
- helper.method(getter).call(files)
49
- end
50
35
  end
51
36
 
52
- Inspec::Targets.add_module('folder', FolderHelper.new)
37
+ Inspec::Targets.add_module('folder', FolderHelper)
53
38
  end
@@ -8,7 +8,7 @@ require 'inspec/targets/archive'
8
8
 
9
9
  module Inspec::Targets
10
10
  class TarHelper < ArchiveHelper
11
- def handles?(target)
11
+ def self.handles?(target)
12
12
  File.file?(target) && target.end_with?('.tar.gz', '.tgz')
13
13
  end
14
14
 
@@ -30,22 +30,20 @@ module Inspec::Targets
30
30
  [files, rootdir]
31
31
  end
32
32
 
33
- def content(input, files, rootdir = nil, opts = {})
34
- content = []
33
+ def content(input, path, rootdir = nil, opts = {})
34
+ content = nil
35
35
  Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
36
36
  tar.each do |entry|
37
37
  if entry.directory?
38
38
  # nothing to do
39
39
  elsif entry.file?
40
- if files.include?(entry.full_name.gsub(rootdir, ''))
41
- h = {
42
- # NB if some file is empty, return empty-string, not nil
43
- content: entry.read || '',
44
- type: opts[:as] || :test,
45
- ref: entry.full_name,
46
- }
47
- content.push(h)
48
- end
40
+ next unless path == entry.full_name.gsub(rootdir, '')
41
+ content = {
42
+ # NB if some file is empty, return empty-string, not nil
43
+ content: entry.read || '',
44
+ type: opts[:as] || :test,
45
+ ref: entry.full_name,
46
+ }
49
47
  elsif entry.header.typeflag == '2'
50
48
  # ignore symlinks for now
51
49
  end
@@ -59,5 +57,5 @@ module Inspec::Targets
59
57
  end
60
58
  end
61
59
 
62
- Inspec::Targets.add_module('tar', TarHelper.new)
60
+ Inspec::Targets.add_module('tar', TarHelper)
63
61
  end
@@ -8,22 +8,22 @@ require 'inspec/targets/archive'
8
8
 
9
9
  module Inspec::Targets
10
10
  class ZipHelper < ArchiveHelper
11
- def handles?(target)
11
+ def self.handles?(target)
12
12
  File.file?(target) and target.end_with?('.zip')
13
13
  end
14
14
 
15
- def content(input, files, rootdir = nil, opts = {})
16
- content = []
15
+ def content(input, path, rootdir = nil, opts = {})
16
+ content = nil
17
17
  ::Zip::InputStream.open(input) do |io|
18
18
  while (entry = io.get_next_entry)
19
- next if !files.include?(entry.name.gsub(rootdir, ''))
20
- h = {
19
+ next unless path == entry.name.gsub(rootdir, '')
20
+ content = {
21
21
  # NB if some file is empty, return empty-string, not nil
22
22
  content: io.read || '',
23
23
  type: opts[:as] || :test,
24
24
  ref: entry.name,
25
25
  }
26
- content.push(h)
26
+ abort
27
27
  end
28
28
  end
29
29
  content
@@ -51,5 +51,5 @@ module Inspec::Targets
51
51
  end
52
52
  end
53
53
 
54
- Inspec::Targets.add_module('zip', ZipHelper.new)
54
+ Inspec::Targets.add_module('zip', ZipHelper)
55
55
  end
@@ -3,5 +3,5 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec
6
- VERSION = '0.11.0'.freeze
6
+ VERSION = '0.12.0'.freeze
7
7
  end
@@ -31,8 +31,8 @@ class IpTables < Inspec.resource(1)
31
31
  "
32
32
 
33
33
  def initialize(params = {})
34
- @table = params[:table] || nil
35
- @chain = params[:chain] || nil
34
+ @table = params[:table]
35
+ @chain = params[:chain]
36
36
 
37
37
  # we're done if we are on linux
38
38
  return if inspec.os.linux?
@@ -43,29 +43,26 @@ class IpTables < Inspec.resource(1)
43
43
  end
44
44
 
45
45
  def has_rule?(rule = nil, _table = nil, _chain = nil)
46
- found = false
47
- retrieve_rules.each { |line|
48
- # checks if the rule is part of the ruleset
49
- # for now, we expect an excact match
50
- found = true if line.casecmp(rule) == 0
51
- }
52
- found
46
+ # checks if the rule is part of the ruleset
47
+ # for now, we expect an exact match
48
+ retrieve_rules.any? { |line| line.casecmp(rule) == 0 }
53
49
  end
54
50
 
55
51
  def retrieve_rules
56
52
  return @iptables_cache if defined?(@iptables_cache)
57
53
 
58
54
  # construct iptables command to read all rules
59
- @table.nil? ? table_cmd = '' : table_cmd = " -t #{@table} "
60
- @chain.nil? ? chain_cmd = '' : chain_cmd = " #{@chain}"
61
- cmd = inspec.command(format('iptables %s -S %s', table_cmd, chain_cmd).strip)
55
+ table_cmd = "-t #{@table}" if @table
56
+ iptables_cmd = format('iptables %s -S %s', table_cmd, @chain).strip
57
+
58
+ cmd = inspec.command(iptables_cmd)
62
59
  return [] if cmd.exit_status.to_i != 0
63
60
 
64
61
  # split rules, returns array or rules
65
- @iptables_cache = cmd.stdout.chomp.split("\n")
62
+ @iptables_cache = cmd.stdout.split("\n").map(&:strip)
66
63
  end
67
64
 
68
65
  def to_s
69
- format('Iptables %s %s', @table.nil? ? '' : "table: #{@table}", @chain.nil? ? '' : "chain: #{@chain}").strip
66
+ format('Iptables %s %s', @table && "table: #{@table}", @chain && "chain: #{@chain}").strip
70
67
  end
71
68
  end
@@ -4,13 +4,59 @@
4
4
  # author: Stephan Renatus
5
5
  # license: All rights reserved
6
6
 
7
- # Usage:
8
- # describe service('dhcp') do
9
- # it { should be_enabled }
10
- # it { should be_installed }
11
- # it { should be_running }
12
- # end
13
- #
7
+ class Runlevels < Hash
8
+ attr_accessor :owner
9
+
10
+ def self.from_hash(owner, hash = {}, filter = nil)
11
+ res = Runlevels.new(owner)
12
+ filter = filter.first if filter.is_a?(Array) && filter.length <= 1
13
+
14
+ ks = case filter
15
+ when nil
16
+ hash.keys
17
+ when Regexp
18
+ hash.keys.find_all { |x| x.to_s =~ filter }
19
+ when Array
20
+ f = filter.map(&:to_s)
21
+ hash.keys.find_all { |x| f.include?(x.to_s) }
22
+ when Numeric
23
+ hash.keys.include?(filter) ? [filter] : []
24
+ else
25
+ hash.keys.find_all { |x| x == filter }
26
+ end
27
+
28
+ ks.each { |k| res[k] = hash[k] }
29
+ res
30
+ end
31
+
32
+ def initialize(owner, default = false)
33
+ @owner = owner
34
+ super(default)
35
+ end
36
+
37
+ def filter(f)
38
+ Runlevels.from_hash(owner, self, f)
39
+ end
40
+
41
+ # Check if all runlevels are enabled
42
+ #
43
+ # @return [boolean] true if all runlevels are enabled
44
+ def enabled?
45
+ values.all?
46
+ end
47
+
48
+ # Check if all runlevels are disabled
49
+ #
50
+ # @return [boolean] true if all runlevels are disabled
51
+ def disabled?
52
+ !values.any?
53
+ end
54
+
55
+ def to_s
56
+ "#{owner} runlevels #{keys.join(', ')}"
57
+ end
58
+ end
59
+
14
60
  # We detect the init system for each operating system, based on the operating
15
61
  # system.
16
62
  #
@@ -29,6 +75,10 @@ class Service < Inspec.resource(1)
29
75
  it { should be_enabled }
30
76
  it { should be_running }
31
77
  end
78
+
79
+ describe service('service_name').runlevels(3, 5) do
80
+ it { should be_enabled }
81
+ end
32
82
  "
33
83
 
34
84
  attr_reader :service_ctl
@@ -98,7 +148,7 @@ class Service < Inspec.resource(1)
98
148
  @cache ||= @service_mgmt.info(@service_name)
99
149
  end
100
150
 
101
- # verifies the service is enabled
151
+ # verifies if the service is enabled
102
152
  def enabled?(_level = nil)
103
153
  return false if info.nil?
104
154
  info[:enabled]
@@ -116,6 +166,12 @@ class Service < Inspec.resource(1)
116
166
  info[:running]
117
167
  end
118
168
 
169
+ # get all runlevels that are available and their configuration
170
+ def runlevels(*args)
171
+ return Runlevels.new(self) if info.nil? or info[:runlevels].nil?
172
+ Runlevels.from_hash(self, info[:runlevels], args)
173
+ end
174
+
119
175
  def to_s
120
176
  "Service #{@service_name}"
121
177
  end
@@ -275,6 +331,8 @@ class Upstart < ServiceManager
275
331
  end
276
332
 
277
333
  class SysV < ServiceManager
334
+ RUNLEVELS = { 0=>false, 1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false }.freeze
335
+
278
336
  def initialize(service_name, service_ctl = nil)
279
337
  @service_ctl ||= 'service'
280
338
  super
@@ -296,10 +354,15 @@ class SysV < ServiceManager
296
354
  # on rhel via: 'chkconfig --list', is not installed by default
297
355
  # bash: for i in `find /etc/rc*.d -name S*`; do basename $i | sed -r 's/^S[0-9]+//'; done | sort | uniq
298
356
  enabled_services_cmd = inspec.command('find /etc/rc*.d -name S*')
299
- enabled_services = enabled_services_cmd.stdout.split("\n").select { |line|
300
- /(^.*#{service_name}.*)/.match(line)
301
- }
302
- enabled = !enabled_services.empty?
357
+ service_line = %r{rc(?<runlevel>[0-6])\.d/S[^/]*?#{Regexp.escape service_name}$}
358
+ all_services = enabled_services_cmd.stdout.split("\n").map { |line|
359
+ service_line.match(line)
360
+ }.compact
361
+ enabled = !all_services.empty?
362
+
363
+ # Determine a list of runlevels which this service is activated for
364
+ runlevels = RUNLEVELS.dup
365
+ all_services.each { |x| runlevels[x[:runlevel].to_i] = true }
303
366
 
304
367
  # check if service is really running
305
368
  # service throws an exit code if the service is not installed or
@@ -313,6 +376,7 @@ class SysV < ServiceManager
313
376
  installed: true,
314
377
  running: running,
315
378
  enabled: enabled,
379
+ runlevels: runlevels,
316
380
  type: 'sysv',
317
381
  }
318
382
  end
@@ -11,6 +11,7 @@ include_recipe('os_prepare::mount')
11
11
  include_recipe('os_prepare::service')
12
12
  include_recipe('os_prepare::package')
13
13
  include_recipe('os_prepare::registry_key')
14
+ include_recipe('os_prepare::iptables')
14
15
 
15
16
  # configure repos, eg. nginx
16
17
  include_recipe('os_prepare::apt')
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ # author: Stephan Renatus
3
+
4
+ case node['platform']
5
+ when 'ubuntu', 'rhel', 'centos', 'fedora'
6
+ execute 'iptables -A INPUT -i eth0 -p tcp -m tcp '\
7
+ '--dport 80 -m state --state NEW -m comment '\
8
+ '--comment "http on 80" -j ACCEPT'
9
+ execute 'iptables -N derby-cognos-web'
10
+ execute 'iptables -A INPUT -j derby-cognos-web'
11
+ execute 'iptables -A derby-cognos-web -p tcp -m tcp --dport 80 '\
12
+ '-m comment --comment "derby-cognos-web" -j ACCEPT'
13
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ case os[:family]
4
+ when 'ubuntu', 'fedora'
5
+ describe iptables do
6
+ it { should have_rule('-A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW -m comment --comment "http on 80" -j ACCEPT') }
7
+ it { should_not have_rule('-A INPUT -i eth1 -p tcp -m tcp --dport 80 -j ACCEPT') }
8
+
9
+ # single-word comments have their quotes dropped
10
+ it { should have_rule('-A derby-cognos-web -p tcp -m tcp --dport 80 -m comment --comment derby-cognos-web -j ACCEPT') }
11
+ end
12
+ when 'rhel', 'centos'
13
+ describe iptables do
14
+ it { should have_rule('-A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW -m comment --comment "http on 80" -j ACCEPT') }
15
+ it { should_not have_rule('-A INPUT -i eth1 -p tcp -m tcp --dport 80 -j ACCEPT') }
16
+ end
17
+
18
+ describe iptables do
19
+ it { should have_rule('-A derby-cognos-web -p tcp -m tcp --dport 80 -m comment --comment "derby-cognos-web" -j ACCEPT') }
20
+ end if os[:release] == 6
21
+
22
+ describe iptables do
23
+ it { should have_rule('-A derby-cognos-web -p tcp -m tcp --dport 80 -m comment --comment derby-cognos-web -j ACCEPT') }
24
+ end if os[:release] == 7
25
+ end
@@ -3,4 +3,4 @@
3
3
  -P OUTPUT ACCEPT
4
4
  -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
5
5
  -A INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -j ACCEPT
6
- -A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW -j ACCEPT
6
+ -A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW -m comment --comment "http like its 1990" -j ACCEPT
@@ -14,6 +14,11 @@ describe 'Inspec::Resources::Iptables' do
14
14
  _(resource.has_rule?('-P OUTPUT DROP')).must_equal false
15
15
  end
16
16
 
17
+ it 'verify iptables with comments on ubuntu' do
18
+ resource = MockLoader.new(:ubuntu1404).load_resource('iptables')
19
+ _(resource.has_rule?('-A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW -m comment --comment "http like its 1990" -j ACCEPT')).must_equal true
20
+ end
21
+
17
22
  it 'verify iptables on windows' do
18
23
  resource = MockLoader.new(:windows).load_resource('iptables')
19
24
  _(resource.has_rule?('-P OUTPUT ACCEPT')).must_equal false
@@ -6,6 +6,7 @@ require 'helper'
6
6
  require 'inspec/resource'
7
7
 
8
8
  describe 'Inspec::Resources::Service' do
9
+ let(:runlevels) { {0=>false, 1=>false, 2=>true, 3=>true, 4=>true, 5=>true, 6=>false} }
9
10
 
10
11
  # windows
11
12
  it 'verify service parsing' do
@@ -58,7 +59,7 @@ describe 'Inspec::Resources::Service' do
58
59
  # centos 6 with sysv
59
60
  it 'verify centos 6 package parsing' do
60
61
  resource = MockLoader.new(:centos6).load_resource('service', 'sshd')
61
- srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' }
62
+ srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' }
62
63
  _(resource.info).must_equal srv
63
64
  _(resource.installed?).must_equal true
64
65
  _(resource.enabled?).must_equal true
@@ -67,7 +68,7 @@ describe 'Inspec::Resources::Service' do
67
68
 
68
69
  it 'verify centos 6 package parsing with default sysv_service' do
69
70
  resource = MockLoader.new(:centos6).load_resource('sysv_service', 'sshd')
70
- srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' }
71
+ srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' }
71
72
  _(resource.info).must_equal srv
72
73
  _(resource.installed?).must_equal true
73
74
  _(resource.enabled?).must_equal true
@@ -125,7 +126,7 @@ describe 'Inspec::Resources::Service' do
125
126
  # debian 7 with systemv
126
127
  it 'verify debian 7 package parsing' do
127
128
  resource = MockLoader.new(:debian7).load_resource('service', 'sshd')
128
- srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' }
129
+ srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' }
129
130
  _(resource.info).must_equal srv
130
131
  _(resource.installed?).must_equal true
131
132
  _(resource.enabled?).must_equal true
@@ -173,7 +174,7 @@ describe 'Inspec::Resources::Service' do
173
174
  # wrlinux
174
175
  it 'verify wrlinux package parsing' do
175
176
  resource = MockLoader.new(:wrlinux).load_resource('service', 'sshd')
176
- srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, type: 'sysv' }
177
+ srv = { name: 'sshd', description: nil, installed: true, running: true, enabled: true, runlevels: runlevels, type: 'sysv' }
177
178
  _(resource.info).must_equal srv
178
179
  _(resource.installed?).must_equal true
179
180
  _(resource.enabled?).must_equal true
@@ -187,4 +188,45 @@ describe 'Inspec::Resources::Service' do
187
188
  _(resource.installed?).must_equal false
188
189
  _(resource.info).must_equal nil
189
190
  end
191
+
192
+ # runlevel detection
193
+ describe 'runlevels on centos 6 (system V)' do
194
+ let(:service) { MockLoader.new(:centos6).load_resource('service', 'sshd') }
195
+
196
+ it 'grabs all runlevels' do
197
+ service.runlevels.keys.must_equal [0, 1, 2, 3, 4, 5, 6]
198
+ end
199
+
200
+ it 'grabs runlevels via filter nil' do
201
+ service.runlevels(nil).keys.must_equal [0, 1, 2, 3, 4, 5, 6]
202
+ end
203
+
204
+ it 'grabs runlevels by number' do
205
+ service.runlevels(3).keys.must_equal [3]
206
+ end
207
+
208
+ it 'grabs runlevels by multiple numbers' do
209
+ service.runlevels(3, 4, 8).keys.must_equal [3, 4]
210
+ end
211
+
212
+ it 'grabs runlevels via regex' do
213
+ service.runlevels(/[5-9]/).keys.must_equal [5, 6]
214
+ end
215
+
216
+ it 'checks enabled true if all services are enabled' do
217
+ service.runlevels(2, 4).enabled?.must_equal true
218
+ end
219
+
220
+ it 'checks enabled false if some services are not enabled' do
221
+ service.runlevels(1, 4).enabled?.must_equal false
222
+ end
223
+
224
+ it 'checks disabled true if all services are disabled' do
225
+ service.runlevels(0, 1).disabled?.must_equal true
226
+ end
227
+
228
+ it 'checks disabled false if some services are not disabled' do
229
+ service.runlevels(0, 4).enabled?.must_equal false
230
+ end
231
+ end
190
232
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-10 00:00:00.000000000 Z
11
+ date: 2016-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: r-train
@@ -223,6 +223,12 @@ files:
223
223
  - lib/bundles/inspec-compliance/cli.rb
224
224
  - lib/bundles/inspec-compliance/configuration.rb
225
225
  - lib/bundles/inspec-compliance/target.rb
226
+ - lib/bundles/inspec-init.rb
227
+ - lib/bundles/inspec-init/cli.rb
228
+ - lib/bundles/inspec-init/templates/profile/README.md
229
+ - lib/bundles/inspec-init/templates/profile/controls/example.rb
230
+ - lib/bundles/inspec-init/templates/profile/inspec.yml
231
+ - lib/bundles/inspec-init/templates/profile/libraries/.gitkeep
226
232
  - lib/bundles/inspec-supermarket/README.md
227
233
  - lib/bundles/inspec-supermarket/api.rb
228
234
  - lib/bundles/inspec-supermarket/cli.rb
@@ -339,6 +345,7 @@ files:
339
345
  - test/integration/cookbooks/os_prepare/recipes/auditctl.rb
340
346
  - test/integration/cookbooks/os_prepare/recipes/default.rb
341
347
  - test/integration/cookbooks/os_prepare/recipes/file.rb
348
+ - test/integration/cookbooks/os_prepare/recipes/iptables.rb
342
349
  - test/integration/cookbooks/os_prepare/recipes/json_yaml_csv_ini.rb
343
350
  - test/integration/cookbooks/os_prepare/recipes/mount.rb
344
351
  - test/integration/cookbooks/os_prepare/recipes/package.rb
@@ -356,6 +363,7 @@ files:
356
363
  - test/integration/test/integration/default/file_spec.rb
357
364
  - test/integration/test/integration/default/group_spec.rb
358
365
  - test/integration/test/integration/default/ini_spec.rb
366
+ - test/integration/test/integration/default/iptables_spec.rb
359
367
  - test/integration/test/integration/default/json_spec.rb
360
368
  - test/integration/test/integration/default/kernel_module_spec.rb
361
369
  - test/integration/test/integration/default/kernel_parameter_spec.rb
@@ -570,6 +578,7 @@ test_files:
570
578
  - test/integration/cookbooks/os_prepare/recipes/auditctl.rb
571
579
  - test/integration/cookbooks/os_prepare/recipes/default.rb
572
580
  - test/integration/cookbooks/os_prepare/recipes/file.rb
581
+ - test/integration/cookbooks/os_prepare/recipes/iptables.rb
573
582
  - test/integration/cookbooks/os_prepare/recipes/json_yaml_csv_ini.rb
574
583
  - test/integration/cookbooks/os_prepare/recipes/mount.rb
575
584
  - test/integration/cookbooks/os_prepare/recipes/package.rb
@@ -587,6 +596,7 @@ test_files:
587
596
  - test/integration/test/integration/default/file_spec.rb
588
597
  - test/integration/test/integration/default/group_spec.rb
589
598
  - test/integration/test/integration/default/ini_spec.rb
599
+ - test/integration/test/integration/default/iptables_spec.rb
590
600
  - test/integration/test/integration/default/json_spec.rb
591
601
  - test/integration/test/integration/default/kernel_module_spec.rb
592
602
  - test/integration/test/integration/default/kernel_parameter_spec.rb