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
@@ -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