inspec 0.12.0 → 0.14.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -2
  3. data/bin/inspec +11 -9
  4. data/docs/matchers.rst +129 -0
  5. data/docs/resources.rst +64 -37
  6. data/inspec.gemspec +1 -1
  7. data/lib/bundles/inspec-compliance/cli.rb +1 -1
  8. data/lib/bundles/inspec-compliance/configuration.rb +1 -0
  9. data/lib/bundles/inspec-compliance/target.rb +16 -32
  10. data/lib/bundles/inspec-init/cli.rb +2 -0
  11. data/lib/bundles/inspec-supermarket.rb +13 -0
  12. data/lib/bundles/inspec-supermarket/api.rb +2 -0
  13. data/lib/bundles/inspec-supermarket/cli.rb +2 -2
  14. data/lib/bundles/inspec-supermarket/target.rb +11 -15
  15. data/lib/fetchers/local.rb +31 -0
  16. data/lib/fetchers/tar.rb +48 -0
  17. data/lib/fetchers/url.rb +100 -0
  18. data/lib/fetchers/zip.rb +47 -0
  19. data/lib/inspec.rb +2 -3
  20. data/lib/inspec/fetcher.rb +22 -0
  21. data/lib/inspec/metadata.rb +4 -2
  22. data/lib/inspec/plugins.rb +2 -0
  23. data/lib/inspec/plugins/fetcher.rb +97 -0
  24. data/lib/inspec/plugins/source_reader.rb +36 -0
  25. data/lib/inspec/profile.rb +92 -81
  26. data/lib/inspec/resource.rb +1 -0
  27. data/lib/inspec/runner.rb +15 -35
  28. data/lib/inspec/source_reader.rb +32 -0
  29. data/lib/inspec/version.rb +1 -1
  30. data/lib/matchers/matchers.rb +5 -6
  31. data/lib/resources/file.rb +8 -2
  32. data/lib/resources/passwd.rb +71 -45
  33. data/lib/resources/service.rb +13 -9
  34. data/lib/resources/shadow.rb +135 -0
  35. data/lib/source_readers/flat.rb +38 -0
  36. data/lib/source_readers/inspec.rb +78 -0
  37. data/lib/utils/base_cli.rb +2 -2
  38. data/lib/utils/parser.rb +1 -1
  39. data/lib/utils/plugin_registry.rb +93 -0
  40. data/test/docker_test.rb +1 -1
  41. data/test/helper.rb +62 -2
  42. data/test/integration/cookbooks/os_prepare/recipes/service.rb +4 -2
  43. data/test/integration/test/integration/default/compare_matcher_spec.rb +11 -0
  44. data/test/integration/test/integration/default/service_spec.rb +16 -1
  45. data/test/unit/fetchers.rb +61 -0
  46. data/test/unit/fetchers/local_test.rb +67 -0
  47. data/test/unit/fetchers/tar_test.rb +36 -0
  48. data/test/unit/fetchers/url_test.rb +152 -0
  49. data/test/unit/fetchers/zip_test.rb +36 -0
  50. data/test/unit/mock/files/passwd +1 -1
  51. data/test/unit/mock/files/shadow +2 -0
  52. data/test/unit/mock/profiles/complete-profile/libraries/testlib.rb +1 -0
  53. data/test/unit/plugin_test.rb +0 -1
  54. data/test/unit/profile_test.rb +32 -53
  55. data/test/unit/resources/passwd_test.rb +69 -14
  56. data/test/unit/resources/shadow_test.rb +67 -0
  57. data/test/unit/source_reader_test.rb +17 -0
  58. data/test/unit/source_readers/flat_test.rb +61 -0
  59. data/test/unit/source_readers/inspec_test.rb +38 -0
  60. data/test/unit/utils/passwd_parser_test.rb +1 -1
  61. metadata +40 -21
  62. data/lib/inspec/targets.rb +0 -10
  63. data/lib/inspec/targets/archive.rb +0 -33
  64. data/lib/inspec/targets/core.rb +0 -56
  65. data/lib/inspec/targets/dir.rb +0 -144
  66. data/lib/inspec/targets/file.rb +0 -33
  67. data/lib/inspec/targets/folder.rb +0 -38
  68. data/lib/inspec/targets/tar.rb +0 -61
  69. data/lib/inspec/targets/url.rb +0 -78
  70. data/lib/inspec/targets/zip.rb +0 -55
  71. data/test/unit/targets.rb +0 -132
@@ -1,10 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'inspec/targets/core'
6
- require 'inspec/targets/file'
7
- require 'inspec/targets/folder'
8
- require 'inspec/targets/url'
9
- require 'inspec/targets/zip'
10
- require 'inspec/targets/tar'
@@ -1,33 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'rubygems/package'
6
- require 'zlib'
7
- require 'inspec/targets/dir'
8
-
9
- module Inspec::Targets
10
- class ArchiveHelper < DirsResolver
11
- attr_reader :files
12
-
13
- def initialize(target, _opts = {})
14
- @target = target
15
- files, @rootdir = structure(target)
16
-
17
- # remove trailing slashes
18
- files = files.collect { |f| f.chomp('/') }
19
-
20
- # remove leading directory
21
- root = Pathname(@rootdir)
22
- @files = files.collect { |f|
23
- Pathname(f).relative_path_from(root).to_s
24
- }
25
- end
26
-
27
- def resolve(path, opts = {})
28
- o = (opts || {})
29
- o[:base_folder] = @target
30
- content(@target, path, @rootdir, o)
31
- end
32
- end
33
- end
@@ -1,56 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'utils/modulator'
6
-
7
- module Inspec
8
- module Targets
9
- extend Modulator
10
-
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
41
- end
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
48
- def self.resolve(targets, opts = {})
49
- Array(targets).map do |target|
50
- handler = find_resolver(target) ||
51
- fail("Don't know how to handle target: #{target}")
52
- handler.resolve(target, opts)
53
- end.flatten
54
- end
55
- end
56
- end
@@ -1,144 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
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 .
17
- module DirsHelper
18
- extend Modulator
19
-
20
- def self.get_handler(paths)
21
- modules.values.reverse.find { |x| x.handles?(paths) }
22
- end
23
- end
24
-
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
33
-
34
- def resolve(_path)
35
- fail NotImplementedError, "Directory resolver #{self.class} must implement #content"
36
- end
37
-
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
46
-
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
- }
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')
85
- end
86
-
87
- def get_libraries(paths)
88
- paths.find_all do |path|
89
- path.start_with?('libraries') && path.end_with?('.rb')
90
- end
91
- end
92
-
93
- def get_filenames(paths)
94
- paths.find_all do |path|
95
- path.start_with?('controls', 'test') && path.end_with?('.rb')
96
- end
97
- end
98
-
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'
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
123
-
124
- def get_filenames(paths)
125
- paths.find_all do |path|
126
- path.start_with? 'spec' and path.end_with? '_spec.rb'
127
- end
128
- end
129
- end
130
- DirsHelper.add_module('serverspec-folder', ServerspecDir.new)
131
-
132
- class FlatDir
133
- def handles?(paths)
134
- get_filenames(paths).empty? == false
135
- end
136
-
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' }
141
- end
142
- end
143
- DirsHelper.add_module('flat-ruby-folder', FlatDir.new)
144
- end
@@ -1,33 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- module Inspec::Targets
6
- class FileHelper
7
- def handles?(target)
8
- File.file?(target) and target.end_with?('.rb')
9
- end
10
-
11
- def resolve(target, opts = {})
12
- base = opts[:base_folder]
13
- path = base.nil? ? target : File.join(base, target)
14
- {
15
- content: File.read(path),
16
- type: opts[:as] || :test,
17
- ref: path,
18
- }
19
- end
20
-
21
- def resolve_all(targets, opts = {})
22
- Array(targets).map do |target|
23
- resolve(target, opts)
24
- end
25
- end
26
-
27
- def to_s
28
- 'File Loader'
29
- end
30
- end
31
-
32
- Inspec::Targets.add_module('file', FileHelper.new)
33
- end
@@ -1,38 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'inspec/targets/dir'
6
- require 'inspec/targets/file'
7
-
8
- module Inspec::Targets
9
- class FolderHelper < DirsResolver
10
- def self.handles?(target)
11
- File.directory?(target)
12
- end
13
-
14
- attr_reader :files
15
-
16
- def initialize(target, _opts = {})
17
- @base_folder = target
18
- # find all files in the folder
19
- files = Dir[File.join(target, '**', '*')]
20
- # remove the prefix
21
- prefix = File.join(target, '')
22
- @files = files.map { |x| x.sub(prefix, '') }
23
- @file_handler = Inspec::Targets.modules['file']
24
- end
25
-
26
- def resolve(path, opts = {})
27
- o = (opts || {})
28
- o[:base_folder] = @base_folder
29
- @file_handler.resolve(path, o)
30
- end
31
-
32
- def to_s
33
- 'Folder Loader'
34
- end
35
- end
36
-
37
- Inspec::Targets.add_module('folder', FolderHelper)
38
- end
@@ -1,61 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'rubygems/package'
6
- require 'zlib'
7
- require 'inspec/targets/archive'
8
-
9
- module Inspec::Targets
10
- class TarHelper < ArchiveHelper
11
- def self.handles?(target)
12
- File.file?(target) && target.end_with?('.tar.gz', '.tgz')
13
- end
14
-
15
- def structure(input)
16
- files = []
17
- rootdir = ''
18
- Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
19
- files = tar.map(&:full_name)
20
- end
21
-
22
- # find root dir of profile
23
- files.each { |f|
24
- pn = Pathname(f)
25
- rootdir = pn.dirname.to_s if pn.basename.to_s == 'inspec.yml' || pn.basename.to_s == 'metadata.rb'
26
- }
27
-
28
- # stores the rootdir of metadata.rb or inspec.yml
29
- rootdir += '/' if !rootdir.empty?
30
- [files, rootdir]
31
- end
32
-
33
- def content(input, path, rootdir = nil, opts = {})
34
- content = nil
35
- Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
36
- tar.each do |entry|
37
- if entry.directory?
38
- # nothing to do
39
- elsif entry.file?
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
- }
47
- elsif entry.header.typeflag == '2'
48
- # ignore symlinks for now
49
- end
50
- end
51
- end
52
- content
53
- end
54
-
55
- def to_s
56
- 'tar.gz Loader'
57
- end
58
- end
59
-
60
- Inspec::Targets.add_module('tar', TarHelper)
61
- end
@@ -1,78 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'uri'
6
- require 'tempfile'
7
- require 'open-uri'
8
- require 'inspec/targets/zip'
9
-
10
- module Inspec::Targets
11
- class UrlHelper
12
- def handles?(target)
13
- uri = URI.parse(target)
14
- return false if uri.nil? or uri.scheme.nil?
15
- %{ http https }.include? uri.scheme
16
- rescue URI::Error => _e
17
- false
18
- end
19
-
20
- def resolve(target, opts = {})
21
- # abort if the target does not start with http or https
22
- return nil unless target.start_with?('https://', 'http://')
23
-
24
- # support for github url
25
- m = %r{^https?://(www\.)?github\.com/(?<user>[\w-]+)/(?<repo>[\w-]+)(\.git)?(/)?$}.match(target)
26
- if m
27
- url = "https://github.com/#{m[:user]}/#{m[:repo]}/archive/master.tar.gz"
28
- else
29
- url = target
30
- end
31
- resolve_archive(url, opts)
32
- end
33
-
34
- # download url into archive using opts,
35
- # returns File object and content-type from HTTP headers
36
- def download_archive(url, archive, opts)
37
- archive.binmode
38
- remote = open(
39
- url,
40
- http_basic_authentication: [opts['user'] || '', opts['password'] || ''],
41
- )
42
-
43
- # download content
44
- archive.write(remote.read)
45
- archive.rewind
46
- archive.close
47
-
48
- [archive, remote.meta['content-type']]
49
- end
50
-
51
- def resolve_archive(url, opts)
52
- archive, content_type = download_archive(url, Tempfile.new(['inspec-dl-', '.tar.gz']), opts)
53
-
54
- # replace extension with zip if we detected a zip content type
55
- if ['application/x-zip-compressed', 'application/zip'].include?(content_type)
56
- # rename file for proper detection in DirHelper
57
- pn = Pathname.new(archive.path)
58
- new_path = pn.dirname.join(pn.basename.to_s.gsub('tar.gz', 'zip'))
59
- File.rename(pn.to_s, new_path.to_s)
60
-
61
- content = ZipHelper.new.resolve(new_path)
62
- File.unlink(new_path)
63
- elsif ['application/x-gzip', 'application/gzip'].include?(content_type)
64
- # use tar helper as default (otherwise returns nil)
65
- content = TarHelper.new.resolve(archive.path)
66
- archive.unlink
67
- end
68
-
69
- content
70
- end
71
-
72
- def to_s
73
- 'URL Loader'
74
- end
75
- end
76
-
77
- Inspec::Targets.add_module('url', UrlHelper.new)
78
- end