inspec 1.14.1 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c359edd56805488af68139d6281b72e60991ee5
4
- data.tar.gz: c06f8586568f4e29cb9b86f5a0b96b8ef58054f3
3
+ metadata.gz: 6d6a9596b5e8f107982bbf0772679d92bd903b24
4
+ data.tar.gz: 87f6fd87e179535f8d78512cc6a35798076cb51f
5
5
  SHA512:
6
- metadata.gz: d3f2d5d72a3e088374e6bd1186453d3d0302827289b10229f0ce736fbf91f3e65c8e5baf6f5cc1856bedf36e97f3f17a37d43a00ac23ac54be4ca3b04abe4c44
7
- data.tar.gz: a91488295b33e93b04669a29166f32923305361dca7e455a8908787549ee9dbd16ac3f395ed22ad76762ea983a5556c03ee3badd1409f009d2b6c55bf5b50983
6
+ metadata.gz: 73b6a1520ce732b59972ab69a1de54febd1512d06cf3102d81ad678ad7bedf85755a214d1ddc803fe7deda9cf2b1f26c0abc49716281f2690f3c4514dea15f3f
7
+ data.tar.gz: 1333f99c255ff0853315186c023fa22d419a3a9ec1e770bc2e6983eed371a15d5f91f3d65ff889deead3cef15d7eec0641fb4b33dc72abaaa1f2c3346de0a173
data/CHANGELOG.md CHANGED
@@ -1,7 +1,43 @@
1
1
  # Change Log
2
2
 
3
- ## [1.14.1](https://github.com/chef/inspec/tree/1.14.1) (2017-02-10)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v1.14.0...1.14.1)
3
+ ## [v1.15.0](https://github.com/chef/inspec/tree/v1.15.0) (2017-02-27)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.14.1...v1.15.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Wrong rendering of InSpec.io header [\#1421](https://github.com/chef/inspec/issues/1421)
9
+
10
+ **Fixed bugs:**
11
+
12
+ - New Inspec.io is crashing on Edge if window is resized to a smaller window [\#1420](https://github.com/chef/inspec/issues/1420)
13
+
14
+ **Closed issues:**
15
+
16
+ - Colours and symbols broken on Windows [\#1508](https://github.com/chef/inspec/issues/1508)
17
+ - be\_reacheable matcher for host resource should not always use ping on linux [\#1504](https://github.com/chef/inspec/issues/1504)
18
+ - Inspec login fails [\#1503](https://github.com/chef/inspec/issues/1503)
19
+ - Develop an inspec test for selinux [\#1496](https://github.com/chef/inspec/issues/1496)
20
+ - Inspec.io: Add webinar/notifications bar in index.html [\#1495](https://github.com/chef/inspec/issues/1495)
21
+ - Inspec.io: Try Demo Button Bug [\#1494](https://github.com/chef/inspec/issues/1494)
22
+ - \[chef-compliance\] Scan Report Calculations [\#1491](https://github.com/chef/inspec/issues/1491)
23
+ - Create url for demo that can be pointed to from outbound campaigns [\#1485](https://github.com/chef/inspec/issues/1485)
24
+ - After inspec update from 1.5 to 1.10 it breaks with \[undefined method `\[\]=' for nil:NilClass\] [\#1456](https://github.com/chef/inspec/issues/1456)
25
+ - Inspec.io and IE11 [\#1437](https://github.com/chef/inspec/issues/1437)
26
+ - Link to robert\_config.rb is broken on inspec.io [\#1226](https://github.com/chef/inspec/issues/1226)
27
+
28
+ **Merged pull requests:**
29
+
30
+ - Fix formatting and colors on Windows [\#1510](https://github.com/chef/inspec/pull/1510) ([trickyearlobe](https://github.com/trickyearlobe))
31
+ - Adding a Habitat profile artifact creator [\#1505](https://github.com/chef/inspec/pull/1505) ([adamleff](https://github.com/adamleff))
32
+ - create inspec.io/tutorial.html [\#1490](https://github.com/chef/inspec/pull/1490) ([arlimus](https://github.com/arlimus))
33
+ - Doc fix for SourceReaders::InspecReader [\#1489](https://github.com/chef/inspec/pull/1489) ([adamleff](https://github.com/adamleff))
34
+ - Generate default profile names, fix bug when using multiple flat profiles [\#1488](https://github.com/chef/inspec/pull/1488) ([adamleff](https://github.com/adamleff))
35
+ - Packages resource support for RedHat [\#1487](https://github.com/chef/inspec/pull/1487) ([alexpop](https://github.com/alexpop))
36
+ - Adding new crontab resource [\#1482](https://github.com/chef/inspec/pull/1482) ([adamleff](https://github.com/adamleff))
37
+ - Provide target info on shell invocation [\#1475](https://github.com/chef/inspec/pull/1475) ([adamleff](https://github.com/adamleff))
38
+
39
+ ## [v1.14.1](https://github.com/chef/inspec/tree/v1.14.1) (2017-02-10)
40
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.14.0...v1.14.1)
5
41
 
6
42
  **Closed issues:**
7
43
 
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: About the crontab Resource
3
+ ---
4
+
5
+ # crontab
6
+
7
+ Use the `crontab` InSpec audit resource to test the crontab entries for a particular user on the system.
8
+
9
+ ## Syntax
10
+
11
+ A `crontab` resource block declares a user (which defaults to the current user, if not specified), and then the details to be tested, such as the schedule elements for each crontab entry or the commands itself:
12
+
13
+ describe crontab do
14
+ its('commands') { should include '/some/scheduled/task.sh' }
15
+ end
16
+
17
+ ## Matchers
18
+
19
+ This InSpec audit resource has the following matchers:
20
+
21
+ ### be
22
+
23
+ <%= partial "/shared/matcher_be" %>
24
+
25
+ ### cmp
26
+
27
+ <%= partial "/shared/matcher_cmp" %>
28
+
29
+ ### eq
30
+
31
+ <%= partial "/shared/matcher_eq" %>
32
+
33
+ ### include
34
+
35
+ <%= partial "/shared/matcher_include" %>
36
+
37
+ ### match
38
+
39
+ <%= partial "/shared/matcher_match" %>
40
+
41
+ ## Examples
42
+
43
+ The following examples show how to use this InSpec audit resource.
44
+
45
+ ### Test that root's crontab has a particular command
46
+
47
+ describe crontab('root') do
48
+ its('commands') { should include '/path/to/some/script' }
49
+ end
50
+
51
+ ### Test that myuser's crontab entry for command '/home/myuser/build.sh' runs every minute
52
+
53
+ describe crontab('myuser').commands('/home/myuser/build.sh') do
54
+ its('hours') { should cmp '*' }
55
+ its('minutes') { should cmp '*' }
56
+ end
57
+
58
+ ### Test that the logged-in user's crontab has no tasks set to run on every hour and every minute
59
+
60
+ describe crontab.where({'hour' => '*', 'minute' => '*'}) do
61
+ its('entries.length') { should cmp '0' }
62
+ end
@@ -3,9 +3,9 @@ lockfile_version: 1
3
3
  depends:
4
4
  - name: profile
5
5
  resolved_source:
6
- path: "/pub/git/inspec/examples/profile"
6
+ path: "/Users/aleff/projects/inspec/examples/profile"
7
7
  version_constraints: ">= 0"
8
8
  - name: profile-attribute
9
9
  resolved_source:
10
- path: "/pub/git/inspec/examples/profile-attribute"
10
+ path: "/Users/aleff/projects/inspec/examples/profile-attribute"
11
11
  version_constraints: ">= 0"
@@ -9,7 +9,7 @@ depends:
9
9
  - name: ssl-benchmark
10
10
  resolved_source:
11
11
  url: https://github.com/dev-sec/ssl-benchmark/archive/master.tar.gz
12
- sha256: 793adcbb91cfc2da0044bb9cbf0863773ae2cf89ce9b8343b4295b137f70897b
12
+ sha256: 9ad48391d4e6efff0a13d06736c5b075fb021410e0a629e087bc21e9617d957c
13
13
  version_constraints: ">= 0"
14
14
  - name: windows-patch-benchmark
15
15
  resolved_source:
data/inspec.gemspec CHANGED
@@ -39,4 +39,5 @@ Gem::Specification.new do |spec|
39
39
  spec.add_dependency 'parallel', '~> 1.9'
40
40
  spec.add_dependency 'rspec_junit_formatter', '~> 0.2.3'
41
41
  spec.add_dependency 'faraday', '>=0.9.0'
42
+ spec.add_dependency 'toml', '~> 0.1'
42
43
  end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ libdir = File.dirname(__FILE__)
5
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
6
+
7
+ module Habitat
8
+ autoload :Log, 'inspec-habitat/log'
9
+ autoload :Profile, 'inspec-habitat/profile'
10
+ end
11
+
12
+ require 'inspec-habitat/cli'
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'thor'
5
+
6
+ module Habitat
7
+ class HabitatProfileCLI < Thor
8
+ namespace 'habitat profile'
9
+
10
+ desc 'create PATH', 'Create a Habitat artifact for the profile found at PATH'
11
+ option :output_dir, type: :string, required: false,
12
+ desc: 'Directory in which to save the generated Habitat artifact. Default: current directory'
13
+ def create(path)
14
+ puts options
15
+ Habitat::Profile.create(path, options)
16
+ end
17
+
18
+ desc 'upload PATH', 'Create a Habitat artifact for the profile found at PATH, and upload it to a Habitat Depot'
19
+ def upload(path)
20
+ Habitat::Profile.upload(path, options)
21
+ end
22
+ end
23
+
24
+ class HabitatCLI < Inspec::BaseCLI
25
+ namespace 'habitat'
26
+
27
+ desc 'profile', 'Manage InSpec profiles as Habitat artifacts'
28
+ subcommand 'profile', HabitatProfileCLI
29
+ end
30
+
31
+ Inspec::Plugins::CLI.add_subcommand(HabitatCLI, 'habitat', 'habitat SUBCOMMAND ...', 'Commands for InSpec + Habitat Integration', {})
32
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'mixlib/log'
5
+
6
+ module Habitat
7
+ class Log
8
+ extend Mixlib::Log
9
+ end
10
+ end
@@ -0,0 +1,334 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'mixlib/shellout'
5
+ require 'toml'
6
+
7
+ module Habitat
8
+ class Profile # rubocop:disable Metrics/ClassLength
9
+ attr_reader :options, :path, :profile
10
+
11
+ def self.create(path, options = {})
12
+ creator = new(path, options)
13
+ hart_file = creator.create
14
+ creator.copy(hart_file)
15
+ ensure
16
+ creator.delete_work_dir
17
+ end
18
+
19
+ def self.upload(path, options = {})
20
+ uploader = new(path, options)
21
+ uploader.upload
22
+ ensure
23
+ uploader.delete_work_dir
24
+ end
25
+
26
+ def initialize(path, options = {})
27
+ @path = path
28
+ @options = options
29
+
30
+ log_level = options.fetch('log_level', 'info')
31
+ Habitat::Log.level(log_level.to_sym)
32
+ end
33
+
34
+ def create
35
+ Habitat::Log.info("Creating a Habitat artifact for profile: #{path}")
36
+
37
+ validate_habitat_installed
38
+ validate_habitat_origin
39
+ create_profile_object
40
+ copy_profile_to_work_dir
41
+ create_plan
42
+ create_run_hook
43
+ create_default_config
44
+
45
+ # returns the path to the .hart file in the work directory
46
+ build_hart
47
+ rescue => e
48
+ Habitat::Log.debug(e.backtrace.join("\n"))
49
+ exit_with_error(
50
+ 'Unable to generate Habitat artifact.',
51
+ "#{e.class} -- #{e.message}",
52
+ )
53
+ end
54
+
55
+ def copy(hart_file)
56
+ validate_output_dir
57
+
58
+ Habitat::Log.info("Copying artifact to #{output_dir}...")
59
+ copy_hart(hart_file)
60
+ end
61
+
62
+ def upload
63
+ validate_habitat_auth_token
64
+ hart_file = create
65
+ upload_hart(hart_file)
66
+ rescue => e
67
+ Habitat::Log.debug(e.backtrace.join("\n"))
68
+ exit_with_error(
69
+ 'Unable to upload Habitat artifact.',
70
+ "#{e.class} -- #{e.message}",
71
+ )
72
+ end
73
+
74
+ def delete_work_dir
75
+ Habitat::Log.debug("Deleting work directory #{work_dir}")
76
+ FileUtils.rm_rf(work_dir) if Dir.exist?(work_dir)
77
+ end
78
+
79
+ private
80
+
81
+ def create_profile_object
82
+ @profile = Inspec::Profile.for_target(path, {})
83
+ end
84
+
85
+ def verify_profile
86
+ Habitat::Log.info('Checking to see if the profile is valid...')
87
+
88
+ unless profile.check[:summary][:valid]
89
+ exit_with_error('Profile check failed. Please fix the profile before creating a Habitat artifact.')
90
+ end
91
+
92
+ Habitat::Log.info('Profile is valid.')
93
+ end
94
+
95
+ def validate_habitat_installed
96
+ Habitat::Log.info('Checking to see if Habitat is installed...')
97
+ cmd = Mixlib::ShellOut.new('hab --version')
98
+ cmd.run_command
99
+ if cmd.error?
100
+ exit_with_error('Unable to run Habitat commands.', cmd.stderr)
101
+ end
102
+ end
103
+
104
+ def validate_habitat_origin
105
+ if habitat_origin.nil?
106
+ exit_with_error(
107
+ 'Unable to determine Habitat origin name.',
108
+ 'Run `hab setup` or set the HAB_ORIGIN environment variable.',
109
+ )
110
+ end
111
+ end
112
+
113
+ def validate_habitat_auth_token
114
+ if habitat_auth_token.nil?
115
+ exit_with_error(
116
+ 'Unable to determine Habitat auth token for publishing.',
117
+ 'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
118
+ )
119
+ end
120
+ end
121
+
122
+ def validate_output_dir
123
+ exit_with_error("Output directory #{output_dir} is not a directory or does not exist.") unless
124
+ File.directory?(output_dir)
125
+ end
126
+
127
+ def work_dir
128
+ return @work_dir if @work_dir
129
+
130
+ @work_dir ||= Dir.mktmpdir('inspec-habitat-exporter')
131
+ Dir.mkdir(File.join(@work_dir, 'src'))
132
+ Dir.mkdir(File.join(@work_dir, 'habitat'))
133
+ Dir.mkdir(File.join(@work_dir, 'habitat', 'hooks'))
134
+ Habitat::Log.debug("Generated work directory #{@work_dir}")
135
+
136
+ @work_dir
137
+ end
138
+
139
+ def copy_profile_to_work_dir
140
+ Habitat::Log.info('Copying profile contents to the work directory...')
141
+ profile.files.each do |f|
142
+ src = File.join(profile.root_path, f)
143
+ dst = File.join(work_dir, 'src', f)
144
+ if File.directory?(f)
145
+ Habitat::Log.debug("Creating directory #{dst}")
146
+ FileUtils.mkdir_p(dst)
147
+ else
148
+ Habitat::Log.debug("Copying file #{src} to #{dst}")
149
+ FileUtils.cp_r(src, dst)
150
+ end
151
+ end
152
+ end
153
+
154
+ def create_plan
155
+ plan_file = File.join(work_dir, 'habitat', 'plan.sh')
156
+ Habitat::Log.info("Generating Habitat plan at #{plan_file}...")
157
+ File.write(plan_file, plan_contents)
158
+ end
159
+
160
+ def create_run_hook
161
+ run_hook_file = File.join(work_dir, 'habitat', 'hooks', 'run')
162
+ Habitat::Log.info("Generating a Habitat run hook at #{run_hook_file}...")
163
+ File.write(run_hook_file, run_hook_contents)
164
+ end
165
+
166
+ def create_default_config
167
+ default_toml = File.join(work_dir, 'habitat', 'default.toml')
168
+ Habitat::Log.info("Generating Habitat's default.toml configuration...")
169
+ File.write(default_toml, 'sleep_time = 300')
170
+ end
171
+
172
+ def build_hart
173
+ Habitat::Log.info('Building our Habitat artifact...')
174
+
175
+ env = {
176
+ 'TERM' => 'vt100',
177
+ 'HAB_ORIGIN' => habitat_origin,
178
+ 'HAB_NONINTERACTIVE' => 'true',
179
+ }
180
+
181
+ env['RUST_LOG'] = 'debug' if Habitat::Log.level == :debug
182
+
183
+ # TODO: Would love to use Mixlib::ShellOut here, but it doesn't
184
+ # seem to preserve the STDIN tty, and docker gets angry.
185
+ Dir.chdir(work_dir) do
186
+ unless system(env, 'hab studio build .')
187
+ exit_with_error('Unable to build the Habitat artifact.')
188
+ end
189
+ end
190
+
191
+ hart_files = Dir.glob(File.join(work_dir, 'results', '*.hart'))
192
+
193
+ if hart_files.length > 1
194
+ exit_with_error('More than one Habitat artifact was created which was not expected.')
195
+ elsif hart_files.empty?
196
+ exit_with_error('No Habitat artifact was created.')
197
+ end
198
+
199
+ hart_files.first
200
+ end
201
+
202
+ def copy_hart(working_dir_hart)
203
+ hart_basename = File.basename(working_dir_hart)
204
+ dst = File.join(output_dir, hart_basename)
205
+ FileUtils.cp(working_dir_hart, dst)
206
+
207
+ dst
208
+ end
209
+
210
+ def upload_hart(hart_file)
211
+ Habitat::Log.info('Uploading the Habitat artifact to our Depot...')
212
+
213
+ env = {
214
+ 'TERM' => 'vt100',
215
+ 'HAB_AUTH_TOKEN' => habitat_auth_token,
216
+ 'HAB_NONINTERACTIVE' => 'true',
217
+ }
218
+
219
+ env['HAB_DEPOT_URL'] = ENV['HAB_DEPOT_URL'] if ENV['HAB_DEPOT_URL']
220
+
221
+ cmd = Mixlib::ShellOut.new("hab pkg upload #{hart_file}", env: env)
222
+ cmd.run_command
223
+ if cmd.error?
224
+ exit_with_error(
225
+ 'Unable to upload Habitat artifact to the Depot.',
226
+ cmd.stdout,
227
+ cmd.stderr,
228
+ )
229
+ end
230
+
231
+ Habitat::Log.info('Upload complete!')
232
+ end
233
+
234
+ def habitat_origin
235
+ ENV['HAB_ORIGIN'] || habitat_cli_config['origin']
236
+ end
237
+
238
+ def habitat_auth_token
239
+ ENV['HAB_AUTH_TOKEN'] || habitat_cli_config['auth_token']
240
+ end
241
+
242
+ def habitat_cli_config
243
+ return @cli_config if @cli_config
244
+
245
+ config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
246
+ return {} unless File.exist?(config_file)
247
+
248
+ @cli_config = TOML.load_file(config_file)
249
+ end
250
+
251
+ def output_dir
252
+ options[:output_dir] || Dir.pwd
253
+ end
254
+
255
+ def exit_with_error(*errors)
256
+ errors.each do |error_msg|
257
+ Habitat::Log.error(error_msg)
258
+ end
259
+
260
+ exit 1
261
+ end
262
+
263
+ def package_name
264
+ "inspec-profile-#{profile.name}"
265
+ end
266
+
267
+ def plan_contents
268
+ plan = <<-EOL
269
+ pkg_name=#{package_name}
270
+ pkg_version=#{profile.version}
271
+ pkg_origin=#{habitat_origin}
272
+ pkg_source="nosuchfile.tar.gz"
273
+ pkg_deps=(chef/inspec)
274
+ pkg_build_deps=()
275
+ EOL
276
+
277
+ plan += "pkg_license='#{profile.metadata.params[:license]}'\n\n" if profile.metadata.params[:license]
278
+
279
+ plan += <<-EOL
280
+ do_download() {
281
+ return 0
282
+ }
283
+
284
+ do_verify() {
285
+ return 0
286
+ }
287
+
288
+ do_unpack() {
289
+ return 0
290
+ }
291
+
292
+ do_build() {
293
+ cp -vr $PLAN_CONTEXT/../src/* $HAB_CACHE_SRC_PATH/$pkg_dirname
294
+ }
295
+
296
+ do_install() {
297
+ cp -R . ${pkg_prefix}/dist
298
+ }
299
+ EOL
300
+
301
+ plan
302
+ end
303
+
304
+ def run_hook_contents
305
+ <<-EOL
306
+ #!/bin/sh
307
+
308
+ export PATH=${PATH}:$(hab pkg path core/ruby)/bin
309
+
310
+ PROFILE_IDENT="#{habitat_origin}/#{package_name}"
311
+ SLEEP_TIME={{cfg.sleep_time}}
312
+
313
+ # InSpec will try to create a .inspec directory, so this needs to be somewhere writable by the hab user
314
+ cd {{pkg.svc_var_path}}
315
+
316
+ while true; do
317
+ echo "Executing InSpec for ${PROFILE_IDENT}"
318
+ hab pkg exec chef/inspec inspec exec $(hab pkg path ${PROFILE_IDENT})/dist --format=cli 2>&1
319
+ RC=$?
320
+
321
+ echo ""
322
+ if [ "x${RC}" == "x0" ]; then
323
+ echo "InSpec run completed successfully."
324
+ else
325
+ echo "InSpec run did NOT complete successfully."
326
+ fi
327
+
328
+ echo "sleeping for ${SLEEP_TIME} seconds"
329
+ sleep ${SLEEP_TIME}
330
+ done
331
+ EOL
332
+ end
333
+ end
334
+ end
@@ -177,13 +177,31 @@ module Inspec
177
177
  end
178
178
  end
179
179
 
180
- def self.finalize(metadata, profile_id, logger = nil)
180
+ def self.finalize_name(metadata, profile_id, original_target)
181
+ # profile_id always overwrites whatever already exists as the name
182
+ unless profile_id.to_s.empty?
183
+ metadata.params[:name] = profile_id.to_s
184
+ return
185
+ end
186
+
187
+ # don't overwrite an existing name
188
+ return unless metadata.params[:name].nil?
189
+
190
+ # if there's a title, there is no need to set a name too
191
+ return unless metadata.params[:title].nil?
192
+
193
+ # create a new name based on the original target if it exists
194
+ metadata.params[:name] = "tests from #{original_target}" unless original_target.to_s.empty?
195
+ end
196
+
197
+ def self.finalize(metadata, profile_id, options, logger = nil)
181
198
  return nil if metadata.nil?
182
199
  param = metadata.params || {}
183
- param['name'] = profile_id.to_s unless profile_id.to_s.empty?
200
+ options ||= {}
184
201
  param['version'] = param['version'].to_s unless param['version'].nil?
185
202
  metadata.params = symbolize_keys(param)
186
203
  metadata.params[:supports] = finalize_supports(metadata.params[:supports], logger)
204
+ finalize_name(metadata, profile_id, options[:target])
187
205
 
188
206
  metadata
189
207
  end
@@ -191,13 +209,13 @@ module Inspec
191
209
  def self.from_yaml(ref, contents, profile_id, logger = nil)
192
210
  res = Metadata.new(ref, logger)
193
211
  res.params = YAML.load(contents)
194
- finalize(res, profile_id, logger)
212
+ finalize(res, profile_id, {}, logger)
195
213
  end
196
214
 
197
215
  def self.from_ruby(ref, contents, profile_id, logger = nil)
198
216
  res = Metadata.new(ref, logger)
199
217
  res.instance_eval(contents, ref, 1)
200
- finalize(res, profile_id, logger)
218
+ finalize(res, profile_id, {}, logger)
201
219
  end
202
220
 
203
221
  def self.from_ref(ref, contents, profile_id, logger = nil)
@@ -82,7 +82,7 @@ module Inspec
82
82
 
83
83
  # rubocop:disable Metrics/AbcSize
84
84
  def initialize(source_reader, options = {})
85
- @target = options.delete(:target)
85
+ @target = options[:target]
86
86
  @logger = options[:logger] || Logger.new(nil)
87
87
  @locked_dependencies = options[:dependencies]
88
88
  @controls = options[:controls] || []
@@ -94,7 +94,7 @@ module Inspec
94
94
  @source_reader = source_reader
95
95
  @tests_collected = false
96
96
  @libraries_loaded = false
97
- Metadata.finalize(@source_reader.metadata, @profile_id)
97
+ Metadata.finalize(@source_reader.metadata, @profile_id, options)
98
98
  @runner_context =
99
99
  options[:profile_context] ||
100
100
  Inspec::ProfileContext.for_profile(self, @backend, @attr_values)
@@ -318,8 +318,6 @@ module Inspec
318
318
 
319
319
  # display all files that will be part of the archive
320
320
  @logger.debug 'Add the following files to archive:'
321
- root_path = @source_reader.target.prefix
322
- files = @source_reader.target.files
323
321
  files.each { |f| @logger.debug ' ' + f }
324
322
 
325
323
  if opts[:zip]
@@ -350,6 +348,14 @@ module Inspec
350
348
  File.join(cwd, 'inspec.lock')
351
349
  end
352
350
 
351
+ def root_path
352
+ @source_reader.target.prefix
353
+ end
354
+
355
+ def files
356
+ @source_reader.target.files
357
+ end
358
+
353
359
  #
354
360
  # TODO(ssd): Relative path handling really needs to be carefully
355
361
  # thought through, especially with respect to relative paths in
@@ -79,6 +79,7 @@ require 'resources/bash'
79
79
  require 'resources/bond'
80
80
  require 'resources/bridge'
81
81
  require 'resources/command'
82
+ require 'resources/crontab'
82
83
  require 'resources/directory'
83
84
  require 'resources/etc_group'
84
85
  require 'resources/file'
@@ -217,7 +217,17 @@ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLengt
217
217
  end
218
218
 
219
219
  def profile_contains_example?(profile, example)
220
- profile[:name] == example[:profile_id]
220
+ profile_name = profile[:name]
221
+ example_profile_id = example[:profile_id]
222
+
223
+ # if either the profile name is nil or the profile in the given example
224
+ # is nil, assume the profile doesn't contain the example and default
225
+ # to creating a new profile. Otherwise, for profiles that have no
226
+ # metadata, this may incorrectly match a profile that does not contain
227
+ # this example, leading to Ruby exceptions.
228
+ return false if profile_name.nil? || example_profile_id.nil?
229
+
230
+ profile_name == example_profile_id
221
231
  end
222
232
 
223
233
  def move_example_into_control(example, control)
@@ -238,27 +248,60 @@ end
238
248
  class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
239
249
  RSpec::Core::Formatters.register self, :close
240
250
 
241
- COLORS = {
242
- 'critical' => "\033[38;5;9m",
243
- 'major' => "\033[38;5;208m",
244
- 'minor' => "\033[0;36m",
245
- 'failed' => "\033[38;5;9m",
246
- 'passed' => "\033[38;5;41m",
247
- 'skipped' => "\033[38;5;247m",
248
- 'reset' => "\033[0m",
249
- }.freeze
250
-
251
- INDICATORS = {
252
- 'critical' => ' × ',
253
- 'major' => ' ∅ ',
254
- 'minor' => ' ⊚ ',
255
- 'failed' => ' × ',
256
- 'skipped' => ' ↺ ',
257
- 'passed' => ' ✔ ',
258
- 'unknown' => ' ? ',
259
- 'empty' => ' ',
260
- 'small' => ' ',
261
- }.freeze
251
+ case RUBY_PLATFORM
252
+ when /windows|mswin|msys|mingw|cygwin/
253
+
254
+ # Most currently available Windows terminals have poor support
255
+ # for ANSI extended colors
256
+ COLORS = {
257
+ 'critical' => "\033[0;1;31m",
258
+ 'major' => "\033[0;1;31m",
259
+ 'minor' => "\033[0;36m",
260
+ 'failed' => "\033[0;1;31m",
261
+ 'passed' => "\033[0;1;32m",
262
+ 'skipped' => "\033[0;37m",
263
+ 'reset' => "\033[0m",
264
+ }.freeze
265
+
266
+ # Most currently available Windows terminals have poor support
267
+ # for UTF-8 characters so use these boring indicators
268
+ INDICATORS = {
269
+ 'critical' => ' [CRIT] ',
270
+ 'major' => ' [MAJR] ',
271
+ 'minor' => ' [MINR] ',
272
+ 'failed' => ' [FAIL] ',
273
+ 'skipped' => ' [SKIP] ',
274
+ 'passed' => ' [PASS] ',
275
+ 'unknown' => ' [UNKN] ',
276
+ 'empty' => ' ',
277
+ 'small' => ' ',
278
+ }.freeze
279
+ else
280
+ # Extended colors for everyone else
281
+ COLORS = {
282
+ 'critical' => "\033[38;5;9m",
283
+ 'major' => "\033[38;5;208m",
284
+ 'minor' => "\033[0;36m",
285
+ 'failed' => "\033[38;5;9m",
286
+ 'passed' => "\033[38;5;41m",
287
+ 'skipped' => "\033[38;5;247m",
288
+ 'reset' => "\033[0m",
289
+ }.freeze
290
+
291
+ # Groovy UTF-8 characters for everyone else...
292
+ # ...even though they probably only work on Mac
293
+ INDICATORS = {
294
+ 'critical' => ' × ',
295
+ 'major' => ' ∅ ',
296
+ 'minor' => ' ⊚ ',
297
+ 'failed' => ' × ',
298
+ 'skipped' => ' ↺ ',
299
+ 'passed' => ' ✔ ',
300
+ 'unknown' => ' ? ',
301
+ 'empty' => ' ',
302
+ 'small' => ' ',
303
+ }.freeze
304
+ end
262
305
 
263
306
  MULTI_TEST_CONTROL_SUMMARY_MAX_LEN = 60
264
307
 
@@ -458,7 +501,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
458
501
  output.puts "Profile: #{profile[:title]} (#{profile[:name] || 'unknown'})"
459
502
  end
460
503
 
461
- output.puts 'Version: ' + (profile[:version] || 'unknown')
504
+ output.puts 'Version: ' + (profile[:version] || '(not specified)')
462
505
  print_target
463
506
  profile[:already_printed] = true
464
507
  end
data/lib/inspec/shell.rb CHANGED
@@ -42,6 +42,8 @@ module Inspec
42
42
  # Add a help menu as the default intro
43
43
  Pry.hooks.add_hook(:before_session, 'inspec_intro') do
44
44
  intro
45
+ print_target_info
46
+ puts
45
47
  end
46
48
 
47
49
  # Track the rules currently registered and what their merge count is.
@@ -94,10 +96,20 @@ module Inspec
94
96
  puts
95
97
  end
96
98
 
99
+ def print_target_info
100
+ ctx = @runner.backend
101
+ puts <<EOF
102
+ You are currently running on:
103
+
104
+ OS platform: #{mark ctx.os[:name] || 'unknown'}
105
+ OS family: #{mark ctx.os[:family] || 'unknown'}
106
+ OS release: #{mark ctx.os[:release] || 'unknown'}
107
+ EOF
108
+ end
109
+
97
110
  def help(resource = nil)
98
111
  if resource.nil?
99
112
 
100
- ctx = @runner.backend
101
113
  puts <<EOF
102
114
 
103
115
  Available commands:
@@ -110,14 +122,9 @@ Available commands:
110
122
  You can use resources in this environment to test the target machine. For example:
111
123
 
112
124
  command('uname -a').stdout
113
- file('/proc/cpuinfo').content => "value",
114
-
115
- You are currently running on:
116
-
117
- OS platform: #{mark ctx.os[:name] || 'unknown'}
118
- OS family: #{mark ctx.os[:family] || 'unknown'}
119
- OS release: #{mark ctx.os[:release] || 'unknown'}
125
+ file('/proc/cpuinfo').content => "value"
120
126
 
127
+ #{print_target_info}
121
128
  EOF
122
129
  elsif resource == 'resources'
123
130
  resources
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.14.1'.freeze
7
+ VERSION = '1.15.0'.freeze
8
8
  end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'utils/parser'
5
+ require 'utils/filter'
6
+
7
+ module Inspec::Resources
8
+ class Crontab < Inspec.resource(1)
9
+ name 'crontab'
10
+ desc 'Use the crontab InSpec audit resource to test the contents of the crontab for a given user which contains information about scheduled tasks owned by that user.'
11
+ example "
12
+ describe crontab('root') do
13
+ its('commands') { should include '/path/to/some/script' }
14
+ end
15
+
16
+ describe crontab('myuser').commands('/home/myuser/build.sh') do
17
+ its('hours') { should cmp '*' }
18
+ its('minutes') { should cmp '*' }
19
+ end
20
+
21
+ describe crontab.where({'hour' => '*', 'minute' => '*'}) do
22
+ its('entries.length') { should cmp '0' }
23
+ end
24
+ "
25
+
26
+ attr_reader :params
27
+
28
+ include CommentParser
29
+
30
+ def initialize(user = nil)
31
+ @user = user
32
+ @params = read_crontab
33
+
34
+ return skip_resource 'The `crontab` resource is not supported on your OS.' unless inspec.os.unix?
35
+ end
36
+
37
+ def read_crontab
38
+ inspec.command(crontab_cmd).stdout.lines.map { |l| parse_crontab_line(l) }.compact
39
+ end
40
+
41
+ def parse_crontab_line(l)
42
+ data, = parse_comment_line(l, comment_char: '#', standalone_comments: false)
43
+ return nil if data.nil? || data.empty?
44
+
45
+ elements = data.split(/\s+/, 6)
46
+ {
47
+ 'minute' => elements.at(0),
48
+ 'hour' => elements.at(1),
49
+ 'day' => elements.at(2),
50
+ 'month' => elements.at(3),
51
+ 'weekday' => elements.at(4),
52
+ 'command' => elements.at(5),
53
+ }
54
+ end
55
+
56
+ def crontab_cmd
57
+ @user.nil? ? 'crontab -l' : "crontab -l -u #{@user}"
58
+ end
59
+
60
+ filter = FilterTable.create
61
+ filter.add_accessor(:where)
62
+ .add_accessor(:entries)
63
+ .add(:minutes, field: 'minute')
64
+ .add(:hours, field: 'hour')
65
+ .add(:days, field: 'day')
66
+ .add(:months, field: 'month')
67
+ .add(:weekdays, field: 'weekday')
68
+ .add(:commands, field: 'command')
69
+
70
+ # rebuild the crontab line from raw content
71
+ filter.add(:content) { |t, _|
72
+ t.entries.map do |e|
73
+ [e.minute, e.hour, e.day, e.month, e.weekday, e.command].join(' ')
74
+ end.join("\n")
75
+ }
76
+
77
+ filter.connect(self, :params)
78
+
79
+ def to_s
80
+ @user.nil? ? 'crontab for current user' : "crontab for user #{@user}"
81
+ end
82
+ end
83
+ end
@@ -29,7 +29,7 @@ module Inspec::Resources
29
29
  os = inspec.os
30
30
  if os.debian?
31
31
  @pkgman = Deb.new(inspec)
32
- elsif %w{redhat suse amazon fedora}.include?(os[:family])
32
+ elsif os.redhat? || %w{suse amazon fedora}.include?(os[:family])
33
33
  @pkgman = Rpm.new(inspec)
34
34
  elsif ['arch'].include?(os[:family])
35
35
  @pkgman = Pacman.new(inspec)
@@ -23,12 +23,17 @@ module Inspec::Resources
23
23
  "
24
24
 
25
25
  def initialize(pattern)
26
- @command = packages_command
27
-
28
- return skip_resource "The packages resource is not yet supported on OS #{inspec.os.name}" unless @command
26
+ os = inspec.os
27
+ if os.debian?
28
+ @pkgs = Debs.new(inspec)
29
+ elsif os.redhat? || %w{suse amazon fedora}.include?(os[:family])
30
+ @pkgs = Rpms.new(inspec)
31
+ else
32
+ return skip_resource "The packages resource is not yet supported on OS #{inspec.os.name}"
33
+ end
29
34
 
30
35
  @pattern = pattern_regexp(pattern)
31
- all_pkgs = build_package_list(@command)
36
+ all_pkgs = @pkgs.build_package_list
32
37
  @list = all_pkgs.find_all do |hm|
33
38
  hm[:name] =~ @pattern
34
39
  end
@@ -48,14 +53,6 @@ module Inspec::Resources
48
53
 
49
54
  private
50
55
 
51
- def packages_command
52
- os = inspec.os
53
- if os.debian?
54
- command = "dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\\n'"
55
- end
56
- command
57
- end
58
-
59
56
  def pattern_regexp(p)
60
57
  if p.class == String
61
58
  Regexp.new(Regexp.escape(p))
@@ -67,21 +64,48 @@ module Inspec::Resources
67
64
  end
68
65
 
69
66
  def filtered_packages
70
- warn "The packages resource is not yet supported on OS #{inspec.os.name}" unless @command
67
+ warn "The packages resource is not yet supported on OS #{inspec.os.name}" if resource_skipped
71
68
  @list
72
69
  end
70
+ end
73
71
 
74
- Package = Struct.new(:status, :name, :version)
72
+ class PkgsManagement
73
+ PackageStruct = Struct.new(:status, :name, :version)
74
+ attr_reader :inspec
75
+ def initialize(inspec)
76
+ @inspec = inspec
77
+ end
78
+ end
75
79
 
76
- def build_package_list(command)
80
+ # Debian / Ubuntu
81
+ class Debs < PkgsManagement
82
+ def build_package_list
83
+ # use two spaces as delimiter in case any of the fields has a space in it
84
+ command = "dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\\n'"
77
85
  cmd = inspec.command(command)
78
- all = cmd.stdout.split("\n")[1..-1]
86
+ all = cmd.stdout.split("\n")
79
87
  return [] if all.nil?
80
88
  all.map do |m|
81
- a = m.split
89
+ a = m.split(/ {2,}/)
82
90
  a[0] = 'installed' if a[0] =~ /^.i/
83
91
  a[2] = a[2].split(':').last
84
- Package.new(*a)
92
+ PackageStruct.new(*a)
93
+ end
94
+ end
95
+ end
96
+
97
+ # RedHat family
98
+ class Rpms < PkgsManagement
99
+ def build_package_list
100
+ # use two spaces as delimiter in case any of the fields has a space in it
101
+ command = "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE}\\n'"
102
+ cmd = inspec.command(command)
103
+ all = cmd.stdout.split("\n")
104
+ return [] if all.nil?
105
+ all.map do |m|
106
+ a = m.split(' ')
107
+ a.unshift('installed')
108
+ PackageStruct.new(*a)
85
109
  end
86
110
  end
87
111
  end
@@ -27,7 +27,7 @@ module SourceReaders
27
27
 
28
28
  # This create a new instance of an InSpec profile source reader
29
29
  #
30
- # @param [SourceReader] target
30
+ # @param [FileProvider] target An instance of a FileProvider object that can list files and read them
31
31
  # @param [String] metadata_source eg. inspec.yml or metadata.rb
32
32
  def initialize(target, metadata_source)
33
33
  @target = target
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.1
4
+ version: 1.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-10 00:00:00.000000000 Z
11
+ date: 2017-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train
@@ -232,6 +232,20 @@ dependencies:
232
232
  - - ">="
233
233
  - !ruby/object:Gem::Version
234
234
  version: 0.9.0
235
+ - !ruby/object:Gem::Dependency
236
+ name: toml
237
+ requirement: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - "~>"
240
+ - !ruby/object:Gem::Version
241
+ version: '0.1'
242
+ type: :runtime
243
+ prerelease: false
244
+ version_requirements: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - "~>"
247
+ - !ruby/object:Gem::Version
248
+ version: '0.1'
235
249
  description: InSpec provides a framework for creating end-to-end infrastructure tests.
236
250
  You can use it for integration or even compliance testing. Create fully portable
237
251
  test profiles and use them in your workflow to ensure stability and security. Integrate
@@ -272,6 +286,7 @@ files:
272
286
  - docs/resources/bridge.md.erb
273
287
  - docs/resources/bsd_service.md.erb
274
288
  - docs/resources/command.md.erb
289
+ - docs/resources/crontab.md.erb
275
290
  - docs/resources/csv.md.erb
276
291
  - docs/resources/directory.md.erb
277
292
  - docs/resources/etc_group.md.erb
@@ -370,7 +385,7 @@ files:
370
385
  - examples/meta-profile/inspec.lock
371
386
  - examples/meta-profile/inspec.yml
372
387
  - examples/meta-profile/vendor/3d473e72d8b70018386a53e0a105e92ccbb4115dc268cadc16ff53d550d2898e.tar.gz
373
- - examples/meta-profile/vendor/793adcbb91cfc2da0044bb9cbf0863773ae2cf89ce9b8343b4295b137f70897b.tar.gz
388
+ - examples/meta-profile/vendor/9ad48391d4e6efff0a13d06736c5b075fb021410e0a629e087bc21e9617d957c.tar.gz
374
389
  - examples/meta-profile/vendor/e25d521fb1093b4c23b31a7dc8f41b5540236f4a433960b151bc427523662ab6.tar.gz
375
390
  - examples/profile-attribute.yml
376
391
  - examples/profile-attribute/README.md
@@ -380,7 +395,6 @@ files:
380
395
  - examples/profile/controls/example.rb
381
396
  - examples/profile/controls/gordon.rb
382
397
  - examples/profile/controls/meta.rb
383
- - examples/profile/inspec.lock
384
398
  - examples/profile/inspec.yml
385
399
  - examples/profile/libraries/gordon_config.rb
386
400
  - inspec.gemspec
@@ -400,6 +414,10 @@ files:
400
414
  - lib/bundles/inspec-compliance/support.rb
401
415
  - lib/bundles/inspec-compliance/target.rb
402
416
  - lib/bundles/inspec-compliance/test/integration/default/cli.rb
417
+ - lib/bundles/inspec-habitat.rb
418
+ - lib/bundles/inspec-habitat/cli.rb
419
+ - lib/bundles/inspec-habitat/log.rb
420
+ - lib/bundles/inspec-habitat/profile.rb
403
421
  - lib/bundles/inspec-init.rb
404
422
  - lib/bundles/inspec-init/README.md
405
423
  - lib/bundles/inspec-init/cli.rb
@@ -474,7 +492,6 @@ files:
474
492
  - lib/inspec/source_reader.rb
475
493
  - lib/inspec/version.rb
476
494
  - lib/matchers/matchers.rb
477
- - lib/resources/.ssh_conf.rb.swp
478
495
  - lib/resources/apache.rb
479
496
  - lib/resources/apache_conf.rb
480
497
  - lib/resources/apt.rb
@@ -485,6 +502,7 @@ files:
485
502
  - lib/resources/bond.rb
486
503
  - lib/resources/bridge.rb
487
504
  - lib/resources/command.rb
505
+ - lib/resources/crontab.rb
488
506
  - lib/resources/csv.rb
489
507
  - lib/resources/directory.rb
490
508
  - lib/resources/etc_group.rb
@@ -575,7 +593,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
575
593
  version: '0'
576
594
  requirements: []
577
595
  rubyforge_project:
578
- rubygems_version: 2.5.2
596
+ rubygems_version: 2.5.1
579
597
  signing_key:
580
598
  specification_version: 4
581
599
  summary: Infrastructure and compliance testing.
@@ -1,3 +0,0 @@
1
- ---
2
- lockfile_version: 1
3
- depends: []
Binary file