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