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
@@ -3,59 +3,43 @@
3
3
  # author: Dominik Richter
4
4
 
5
5
  require 'uri'
6
+ require 'inspec/fetcher'
7
+ require 'fetchers/url'
6
8
 
7
9
  # InSpec Target Helper for Chef Compliance
8
10
  # reuses UrlHelper, but it knows the target server and the access token already
9
11
  # similar to `inspec exec http://localhost:2134/owners/%base%/compliance/%ssh%/tar --user %token%`
10
12
  module Compliance
11
- class ChefComplianceHelper < Inspec::Targets::UrlHelper
12
- def handles?(target)
13
+ class Fetcher < Fetchers::Url
14
+ name 'compliance'
15
+ priority 500
16
+
17
+ def self.resolve(target, opts = {})
13
18
  # check for local scheme compliance://
14
19
  uri = URI(target)
15
- return unless URI(uri).scheme == 'compliance'
20
+ return nil unless URI(uri).scheme == 'compliance'
16
21
 
17
22
  # check if we have a compliance token
18
23
  config = Compliance::Configuration.new
19
- return if config['token'].nil?
20
-
21
- # get profile name
22
- profile = get_profile_name(uri)
24
+ return nil if config['token'].nil?
23
25
 
24
26
  # verifies that the target e.g base/ssh exists
27
+ profile = uri.host + uri.path
25
28
  Compliance::API.exist?(profile)
26
- rescue URI::Error => _e
27
- false
28
- end
29
29
 
30
- # generates proper url
31
- def resolve(target, opts = {})
32
- profile = get_profile_name(URI(target))
33
- # generates server url
34
- target = build_target_url(profile)
35
- config = Compliance::Configuration.new
36
30
  opts['user'] = config['token']
37
- puts target
38
- super(target, opts)
31
+ super(target_url(config, profile), opts)
32
+ rescue URI::Error => _e
33
+ nil
39
34
  end
40
35
 
41
- # extracts profile name from url
42
- def get_profile_name(uri)
43
- uri.host + uri.path
44
- end
45
-
46
- def build_target_url(target)
47
- owner, profile = target.split('/')
48
- config = Compliance::Configuration.new
49
- url = "#{config['server']}/owners/%owner_name%/compliance/%profile_name%/tar"
50
- .gsub('%owner_name%', owner)
51
- .gsub('%profile_name%', profile)
52
- url
36
+ def self.target_url(config, profile)
37
+ owner, id = profile.split('/')
38
+ "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
53
39
  end
54
40
 
55
41
  def to_s
56
42
  'Chef Compliance Profile Loader'
57
43
  end
58
44
  end
59
-
60
- Inspec::Targets.add_module('chefcompliance', ChefComplianceHelper.new)
61
45
  end
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
  # author: Christoph Hartmann
3
3
 
4
+ require 'pathname'
5
+
4
6
  module Init
5
7
  class CLI < Inspec::BaseCLI
6
8
  namespace 'init'
@@ -0,0 +1,13 @@
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
+ module Supermarket
9
+ autoload :API, 'inspec-supermarket/api'
10
+ end
11
+
12
+ require 'inspec-supermarket/cli'
13
+ require 'inspec-supermarket/target'
@@ -2,6 +2,8 @@
2
2
  # author: Christoph Hartmann
3
3
  # author: Dominik Richter
4
4
 
5
+ require 'net/http'
6
+
5
7
  module Supermarket
6
8
  class API
7
9
  SUPERMARKET_URL = 'https://supermarket.chef.io'.freeze
@@ -29,7 +29,7 @@ module Supermarket
29
29
 
30
30
  # execute profile from inspec exec implementation
31
31
  diagnose
32
- run_tests(opts, tests)
32
+ run_tests(tests, opts)
33
33
  end
34
34
 
35
35
  desc 'info profile', 'display profile details'
@@ -45,5 +45,5 @@ module Supermarket
45
45
  end
46
46
 
47
47
  # register the subcommand to Inspec CLI registry
48
- Inspec::Plugins::CLI.add_subcommand(Supermarket::SupermarketCLI, 'supermarket', 'supermarket SUBCOMMAND ...', 'Supermarket commands', {})
48
+ Inspec::Plugins::CLI.add_subcommand(SupermarketCLI, 'supermarket', 'supermarket SUBCOMMAND ...', 'Supermarket commands', {})
49
49
  end
@@ -3,30 +3,26 @@
3
3
  # author: Dominik Richter
4
4
 
5
5
  require 'uri'
6
+ require 'inspec/fetcher'
7
+ require 'fetchers/url'
6
8
 
7
9
  # InSpec Target Helper for Supermarket
8
10
  module Supermarket
9
- class SupermarketHelper < Inspec::Targets::UrlHelper
10
- def handles?(profile)
11
- # check for local scheme supermarket://
12
- return unless URI(profile).scheme == 'supermarket'
11
+ class Fetcher < Fetchers::Url
12
+ name 'supermarket'
13
+ priority 500
13
14
 
14
- # verifies that the target e.g base/ssh exists
15
- Supermarket::API.exist?(profile)
16
- rescue URI::Error => _e
17
- false
18
- end
19
-
20
- # generates proper url
21
- def resolve(profile, opts = {})
22
- tool_info = Supermarket::API.find(profile)
15
+ def self.resolve(target, opts = {})
16
+ return nil unless URI(target).scheme == 'supermarket'
17
+ return nil unless Supermarket::API.exist?(target)
18
+ tool_info = Supermarket::API.find(target)
23
19
  super(tool_info['tool_source_url'], opts)
20
+ rescue URI::Error => _e
21
+ nil
24
22
  end
25
23
 
26
24
  def to_s
27
25
  'Chef Compliance Profile Loader'
28
26
  end
29
27
  end
30
-
31
- Inspec::Targets.add_module('supermarket', Supermarket::SupermarketHelper.new)
32
28
  end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ module Fetchers
6
+ class Local < Inspec.fetcher(1)
7
+ name 'local'
8
+ priority 0
9
+
10
+ attr_reader :files
11
+
12
+ def self.resolve(target)
13
+ return nil unless File.exist?(target)
14
+ new(target)
15
+ end
16
+
17
+ def initialize(target)
18
+ if File.file?(target)
19
+ @files = [target]
20
+ else
21
+ @files = Dir[File.join(target, '**', '*')]
22
+ end
23
+ end
24
+
25
+ def read(file)
26
+ return nil unless files.include?(file)
27
+ return nil unless File.file?(file)
28
+ File.read(file)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'rubygems/package'
6
+ require 'zlib'
7
+
8
+ module Fetchers
9
+ class Tar < Inspec.fetcher(1)
10
+ name 'tar'
11
+ priority 100
12
+
13
+ attr_reader :files
14
+
15
+ def self.resolve(target)
16
+ return nil unless File.file?(target)
17
+ return nil unless target.end_with?('.tar.gz', '.tgz')
18
+ new(target)
19
+ end
20
+
21
+ def initialize(target)
22
+ @target = target
23
+ @contents = {}
24
+ @files = []
25
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(@target)) do |tar|
26
+ @files = tar.map(&:full_name)
27
+ end
28
+ end
29
+
30
+ def read(file)
31
+ @contents[file] ||= read_from_tar(file)
32
+ end
33
+
34
+ def read_from_tar(file)
35
+ return nil unless @files.include?(file)
36
+ res = nil
37
+ # NB `TarReader` includes `Enumerable` beginning with Ruby 2.x
38
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(@target)) do |tar|
39
+ tar.each do |entry|
40
+ next unless entry.file? && file == entry.full_name
41
+ res = entry.read
42
+ break
43
+ end
44
+ end
45
+ res
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'uri'
6
+ require 'tempfile'
7
+ require 'open-uri'
8
+
9
+ module Fetchers
10
+ class Url < Inspec.fetcher(1)
11
+ name 'url'
12
+ priority 200
13
+
14
+ attr_reader :files
15
+
16
+ def self.resolve(target, opts = {})
17
+ uri = URI.parse(target)
18
+ return nil if uri.nil? or uri.scheme.nil?
19
+ return nil unless %{ http https }.include? uri.scheme
20
+ target = transform(target)
21
+ # fetch this url and hand it off
22
+ res = new(target, opts)
23
+ resolve_next(res.archive.path, res)
24
+ rescue URI::Error => _e
25
+ nil
26
+ end
27
+
28
+ # Transforms a browser github url to github tar url
29
+ # We distinguish between three different Github URL types:
30
+ # - Master URL
31
+ # - Branch URL
32
+ # - Commit URL
33
+ #
34
+ # master url:
35
+ # https://github.com/nathenharvey/tmp_compliance_profile/ is transformed to
36
+ # https://github.com/nathenharvey/tmp_compliance_profile/archive/master.tar.gz
37
+ #
38
+ # github branch:
39
+ # https://github.com/hardening-io/tests-os-hardening/tree/2.0 is transformed to
40
+ # https://github.com/hardening-io/tests-os-hardening/archive/2.0.tar.gz
41
+ #
42
+ # github commit:
43
+ # https://github.com/hardening-io/tests-os-hardening/tree/48bd4388ddffde68badd83aefa654e7af3231876
44
+ # is transformed to
45
+ # https://github.com/hardening-io/tests-os-hardening/archive/48bd4388ddffde68badd83aefa654e7af3231876.tar.gz
46
+ def self.transform(target)
47
+ # support for default github url
48
+ m = %r{^https?://(www\.)?github\.com/(?<user>[\w-]+)/(?<repo>[\w-]+)(\.git)?(/)?$}.match(target)
49
+ return "https://github.com/#{m[:user]}/#{m[:repo]}/archive/master.tar.gz" if m
50
+
51
+ # support for branch and commit urls
52
+ m = %r{^https?://(www\.)?github\.com/(?<user>[\w-]+)/(?<repo>[\w-]+)/tree/(?<commit>[\w\.]+)(/)?$}.match(target)
53
+ return "https://github.com/#{m[:user]}/#{m[:repo]}/archive/#{m[:commit]}.tar.gz" if m
54
+
55
+ # if we could not find a match, return the original value
56
+ target
57
+ end
58
+
59
+ MIME_TYPES = {
60
+ 'application/x-zip-compressed' => '.zip',
61
+ 'application/zip' => '.zip',
62
+ 'application/x-gzip' => '.tar.gz',
63
+ 'application/gzip' => '.tar.gz',
64
+ }.freeze
65
+
66
+ # download url into archive using opts,
67
+ # returns File object and content-type from HTTP headers
68
+ def self.download_archive(url, opts)
69
+ remote = open(
70
+ url,
71
+ http_basic_authentication: [opts['user'] || '', opts['password'] || ''],
72
+ )
73
+
74
+ content_type = remote.meta['content-type']
75
+ file_type = MIME_TYPES[content_type] ||
76
+ throw(RuntimeError, 'Failed to resolve URL target, its '\
77
+ "metadata did not match ZIP or TAR: #{content_type}")
78
+
79
+ # fall back to tar
80
+ if file_type.nil?
81
+ fail "Could not determine file type for content type #{content_type}."
82
+ end
83
+
84
+ # download content
85
+ archive = Tempfile.new(['inspec-dl-', file_type])
86
+ archive.binmode
87
+ archive.write(remote.read)
88
+ archive.rewind
89
+ archive.close
90
+ archive
91
+ end
92
+
93
+ attr_reader :archive
94
+
95
+ def initialize(url, opts)
96
+ @target = url
97
+ @archive = self.class.download_archive(url, opts)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'zip'
6
+
7
+ module Fetchers
8
+ class Zip < Inspec.fetcher(1)
9
+ name 'zip'
10
+ priority 100
11
+
12
+ attr_reader :files
13
+
14
+ def self.resolve(target)
15
+ return nil unless File.file?(target) and target.end_with?('.zip')
16
+ new(target)
17
+ end
18
+
19
+ def initialize(target)
20
+ @target = target
21
+ @contents = {}
22
+ @files = []
23
+ ::Zip::InputStream.open(@target) do |io|
24
+ while (entry = io.get_next_entry)
25
+ @files.push(entry.name.sub(%r{/+$}, ''))
26
+ end
27
+ end
28
+ end
29
+
30
+ def read(file)
31
+ @contents[file] ||= read_from_zip(file)
32
+ end
33
+
34
+ def read_from_zip(file)
35
+ return nil unless @files.include?(file)
36
+ res = nil
37
+ ::Zip::InputStream.open(@target) do |io|
38
+ while (entry = io.get_next_entry)
39
+ next unless file == entry.name
40
+ res = io.read
41
+ break
42
+ end
43
+ end
44
+ res
45
+ end
46
+ end
47
+ end
data/lib/inspec.rb CHANGED
@@ -20,8 +20,7 @@ require 'inspec/shell'
20
20
 
21
21
  # all utils that may be required by plugins
22
22
  require 'utils/base_cli'
23
-
24
- # ensure resource and plugins are loaded after runner, because the runner loads
25
- # targets
23
+ require 'inspec/fetcher'
24
+ require 'inspec/source_reader'
26
25
  require 'inspec/resource'
27
26
  require 'inspec/plugins'
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'inspec/plugins'
6
+ require 'utils/plugin_registry'
7
+
8
+ module Inspec
9
+ Fetcher = PluginRegistry.new
10
+
11
+ def self.fetcher(version)
12
+ if version != 1
13
+ fail 'Only fetcher version 1 is supported!'
14
+ end
15
+ Inspec::Plugins::Fetcher
16
+ end
17
+ end
18
+
19
+ require 'fetchers/local'
20
+ require 'fetchers/zip'
21
+ require 'fetchers/tar'
22
+ require 'fetchers/url'
@@ -140,8 +140,10 @@ module Inspec
140
140
  end
141
141
 
142
142
  def self.finalize(metadata, profile_id)
143
- metadata.params['name'] = profile_id.to_s unless profile_id.to_s.empty?
144
- metadata.params = symbolize_keys(metadata.params || {})
143
+ param = metadata.params || {}
144
+ param['name'] = profile_id.to_s unless profile_id.to_s.empty?
145
+ param['version'] = param['version'].to_s unless param['version'].nil?
146
+ metadata.params = symbolize_keys(param)
145
147
  metadata
146
148
  end
147
149