inspec 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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