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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -2
- data/bin/inspec +11 -9
- data/docs/matchers.rst +129 -0
- data/docs/resources.rst +64 -37
- data/inspec.gemspec +1 -1
- data/lib/bundles/inspec-compliance/cli.rb +1 -1
- data/lib/bundles/inspec-compliance/configuration.rb +1 -0
- data/lib/bundles/inspec-compliance/target.rb +16 -32
- data/lib/bundles/inspec-init/cli.rb +2 -0
- data/lib/bundles/inspec-supermarket.rb +13 -0
- data/lib/bundles/inspec-supermarket/api.rb +2 -0
- data/lib/bundles/inspec-supermarket/cli.rb +2 -2
- data/lib/bundles/inspec-supermarket/target.rb +11 -15
- data/lib/fetchers/local.rb +31 -0
- data/lib/fetchers/tar.rb +48 -0
- data/lib/fetchers/url.rb +100 -0
- data/lib/fetchers/zip.rb +47 -0
- data/lib/inspec.rb +2 -3
- data/lib/inspec/fetcher.rb +22 -0
- data/lib/inspec/metadata.rb +4 -2
- data/lib/inspec/plugins.rb +2 -0
- data/lib/inspec/plugins/fetcher.rb +97 -0
- data/lib/inspec/plugins/source_reader.rb +36 -0
- data/lib/inspec/profile.rb +92 -81
- data/lib/inspec/resource.rb +1 -0
- data/lib/inspec/runner.rb +15 -35
- data/lib/inspec/source_reader.rb +32 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +5 -6
- data/lib/resources/file.rb +8 -2
- data/lib/resources/passwd.rb +71 -45
- data/lib/resources/service.rb +13 -9
- data/lib/resources/shadow.rb +135 -0
- data/lib/source_readers/flat.rb +38 -0
- data/lib/source_readers/inspec.rb +78 -0
- data/lib/utils/base_cli.rb +2 -2
- data/lib/utils/parser.rb +1 -1
- data/lib/utils/plugin_registry.rb +93 -0
- data/test/docker_test.rb +1 -1
- data/test/helper.rb +62 -2
- data/test/integration/cookbooks/os_prepare/recipes/service.rb +4 -2
- data/test/integration/test/integration/default/compare_matcher_spec.rb +11 -0
- data/test/integration/test/integration/default/service_spec.rb +16 -1
- data/test/unit/fetchers.rb +61 -0
- data/test/unit/fetchers/local_test.rb +67 -0
- data/test/unit/fetchers/tar_test.rb +36 -0
- data/test/unit/fetchers/url_test.rb +152 -0
- data/test/unit/fetchers/zip_test.rb +36 -0
- data/test/unit/mock/files/passwd +1 -1
- data/test/unit/mock/files/shadow +2 -0
- data/test/unit/mock/profiles/complete-profile/libraries/testlib.rb +1 -0
- data/test/unit/plugin_test.rb +0 -1
- data/test/unit/profile_test.rb +32 -53
- data/test/unit/resources/passwd_test.rb +69 -14
- data/test/unit/resources/shadow_test.rb +67 -0
- data/test/unit/source_reader_test.rb +17 -0
- data/test/unit/source_readers/flat_test.rb +61 -0
- data/test/unit/source_readers/inspec_test.rb +38 -0
- data/test/unit/utils/passwd_parser_test.rb +1 -1
- metadata +40 -21
- data/lib/inspec/targets.rb +0 -10
- data/lib/inspec/targets/archive.rb +0 -33
- data/lib/inspec/targets/core.rb +0 -56
- data/lib/inspec/targets/dir.rb +0 -144
- data/lib/inspec/targets/file.rb +0 -33
- data/lib/inspec/targets/folder.rb +0 -38
- data/lib/inspec/targets/tar.rb +0 -61
- data/lib/inspec/targets/url.rb +0 -78
- data/lib/inspec/targets/zip.rb +0 -55
- 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
|
12
|
-
|
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
|
-
|
38
|
-
|
31
|
+
super(target_url(config, profile), opts)
|
32
|
+
rescue URI::Error => _e
|
33
|
+
nil
|
39
34
|
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
@@ -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'
|
@@ -29,7 +29,7 @@ module Supermarket
|
|
29
29
|
|
30
30
|
# execute profile from inspec exec implementation
|
31
31
|
diagnose
|
32
|
-
run_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(
|
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
|
10
|
-
|
11
|
-
|
12
|
-
return unless URI(profile).scheme == 'supermarket'
|
11
|
+
class Fetcher < Fetchers::Url
|
12
|
+
name 'supermarket'
|
13
|
+
priority 500
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
data/lib/fetchers/tar.rb
ADDED
@@ -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
|
data/lib/fetchers/url.rb
ADDED
@@ -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
|
data/lib/fetchers/zip.rb
ADDED
@@ -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
|
-
|
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'
|
data/lib/inspec/metadata.rb
CHANGED
@@ -140,8 +140,10 @@ module Inspec
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def self.finalize(metadata, profile_id)
|
143
|
-
|
144
|
-
|
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
|
|