inspec-core 2.2.102 → 2.2.112

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -7
  3. data/docs/profiles.md +9 -0
  4. data/docs/resources/gem.md.erb +24 -5
  5. data/docs/resources/mssql_session.md.erb +8 -0
  6. data/lib/inspec/plugin/v2/loader.rb +33 -7
  7. data/lib/inspec/reporters/json_automate.rb +1 -1
  8. data/lib/inspec/version.rb +1 -1
  9. data/lib/plugins/README.md +16 -0
  10. data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +162 -0
  11. data/lib/plugins/inspec-artifact/lib/inspec-artifact/cli.rb +114 -0
  12. data/lib/plugins/inspec-artifact/lib/inspec-artifact.rb +12 -0
  13. data/lib/plugins/inspec-artifact/test/functional/inspec_artifact_test.rb +46 -0
  14. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +39 -0
  15. data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +394 -0
  16. data/lib/plugins/inspec-habitat/lib/inspec-habitat.rb +11 -0
  17. data/lib/plugins/inspec-habitat/test/unit/profile_test.rb +184 -0
  18. data/lib/{bundles → plugins}/inspec-init/README.md +0 -0
  19. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +28 -0
  20. data/lib/plugins/inspec-init/lib/inspec-init/renderer.rb +81 -0
  21. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/README.md +0 -0
  22. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/controls/example.rb +0 -0
  23. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/inspec.yml +0 -0
  24. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/libraries/.gitkeep +0 -0
  25. data/lib/plugins/inspec-init/lib/inspec-init.rb +12 -0
  26. data/lib/plugins/inspec-init/test/functional/inspec_init_test.rb +30 -0
  27. data/lib/plugins/shared/core_plugin_test_helper.rb +50 -0
  28. data/lib/resources/gem.rb +7 -1
  29. data/lib/resources/mssql_session.rb +4 -2
  30. metadata +21 -17
  31. data/lib/bundles/inspec-artifact/README.md +0 -1
  32. data/lib/bundles/inspec-artifact/cli.rb +0 -278
  33. data/lib/bundles/inspec-artifact.rb +0 -7
  34. data/lib/bundles/inspec-habitat/cli.rb +0 -37
  35. data/lib/bundles/inspec-habitat/log.rb +0 -10
  36. data/lib/bundles/inspec-habitat/profile.rb +0 -391
  37. data/lib/bundles/inspec-habitat.rb +0 -12
  38. data/lib/bundles/inspec-init/cli.rb +0 -39
  39. data/lib/bundles/inspec-init/renderer.rb +0 -79
  40. data/lib/bundles/inspec-init.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 536fc0f8a749210da73f4908b57c9c3f032541ff2b8c27b293fdea23e138f518
4
- data.tar.gz: 27c338de9ab67b4c28e8dfa70e503e9d89b0a730008fb96a1a55d1d441b79bee
3
+ metadata.gz: '08e8c25f0f329b43923160911ebafc36bebe17e7f7a09d5f4254a3534849429e'
4
+ data.tar.gz: 9238855161cf160af08a51119847757864374c75f6dfcbef2a2abec6e8033358
5
5
  SHA512:
6
- metadata.gz: 12215a7ecd6b95a6a06d111079ef6343fa08840a3a357ac36be0b56c401ce9671137db9575cb62a334b32205cd2b17db20ef7193ff64ce98e71ceb2b80cf622d
7
- data.tar.gz: 1887e20247d05651d7793338d8c39b705ce1cd76d1fb6fbf98fa0b293df6153c2655bf3d361af73893c1cfc1880bcc9bc9d06c3b5bc399e96c2225eb2aa0c313
6
+ metadata.gz: 648d001c2eea839d426543d34f80da62928abe93a9ffbf6de4838a1e100b4aaa16475dc16bf93fb647f8f0e41f2db02eab834678a0f88c628d358dfdfbd6bbf8
7
+ data.tar.gz: c170162f6a199addb3fb8595e6e265a2e14efb715ec8eb890cc827cbe248d07bd516b83f81b0f431b28e18f1a645a1586cabf7d4177043fa7e55339bdd8c543d
data/CHANGELOG.md CHANGED
@@ -1,20 +1,39 @@
1
1
  # Change Log
2
2
  <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ -->
3
- <!-- latest_release 2.2.102 -->
4
- ## [v2.2.102](https://github.com/inspec/inspec/tree/v2.2.102) (2018-09-17)
3
+ <!-- latest_release 2.2.112 -->
4
+ ## [v2.2.112](https://github.com/inspec/inspec/tree/v2.2.112) (2018-09-19)
5
5
 
6
6
  #### Merged Pull Requests
7
- - Add json-automate to the report method [#3401](https://github.com/inspec/inspec/pull/3401) ([jquick](https://github.com/jquick))
7
+ - Move artifact to v2 plugin [#3406](https://github.com/inspec/inspec/pull/3406) ([jquick](https://github.com/jquick))
8
8
  <!-- latest_release -->
9
9
 
10
- <!-- release_rollup since=2.2.101 -->
11
- ### Changes since 2.2.101 release
10
+ <!-- release_rollup since=2.2.102 -->
11
+ ### Changes since 2.2.102 release
12
+
13
+ #### Enhancements
14
+ - adding `versions` to the `gem` resource [#3398](https://github.com/inspec/inspec/pull/3398) ([majormoses](https://github.com/majormoses)) <!-- 2.2.107 -->
15
+ - Plugins: Add support for &#39;bundles&#39; migration [#3384](https://github.com/inspec/inspec/pull/3384) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 2.2.105 -->
16
+
17
+ #### New Features
18
+ - Update AWS Security Group to work with IPV6 rules. [#3394](https://github.com/inspec/inspec/pull/3394) ([MartinLogan](https://github.com/MartinLogan)) <!-- 2.2.111 -->
19
+ - Added db_name flag [#3383](https://github.com/inspec/inspec/pull/3383) ([kdoores](https://github.com/kdoores)) <!-- 2.2.104 -->
12
20
 
13
21
  #### Merged Pull Requests
14
- - Add json-automate to the report method [#3401](https://github.com/inspec/inspec/pull/3401) ([jquick](https://github.com/jquick)) <!-- 2.2.102 -->
22
+ - Move artifact to v2 plugin [#3406](https://github.com/inspec/inspec/pull/3406) ([jquick](https://github.com/jquick)) <!-- 2.2.112 -->
23
+ - Move inspec init to v2 plugins [#3407](https://github.com/inspec/inspec/pull/3407) ([jquick](https://github.com/jquick)) <!-- 2.2.110 -->
24
+ - Fix gem tests from recent merge [#3409](https://github.com/inspec/inspec/pull/3409) ([jquick](https://github.com/jquick)) <!-- 2.2.109 -->
25
+ - Fix json automate tests and render call [#3408](https://github.com/inspec/inspec/pull/3408) ([jquick](https://github.com/jquick)) <!-- 2.2.108 -->
26
+ - Move habitat to v2 plugin [#3404](https://github.com/inspec/inspec/pull/3404) ([jquick](https://github.com/jquick)) <!-- 2.2.106 -->
27
+ - Fix rendering of profiles docs [#3393](https://github.com/inspec/inspec/pull/3393) ([jquick](https://github.com/jquick)) <!-- 2.2.103 -->
15
28
  <!-- release_rollup -->
16
29
 
17
30
  <!-- latest_stable_release -->
31
+ ## [v2.2.102](https://github.com/inspec/inspec/tree/v2.2.102) (2018-09-17)
32
+
33
+ #### Merged Pull Requests
34
+ - Add json-automate to the report method [#3401](https://github.com/inspec/inspec/pull/3401) ([jquick](https://github.com/jquick))
35
+ <!-- latest_stable_release -->
36
+
18
37
  ## [v2.2.101](https://github.com/inspec/inspec/tree/v2.2.101) (2018-09-14)
19
38
 
20
39
  #### New Features
@@ -44,7 +63,6 @@
44
63
  - Bump omnibus ruby to 2.5.1 [#3390](https://github.com/inspec/inspec/pull/3390) ([jquick](https://github.com/jquick))
45
64
  - Add platforms schema command [#3346](https://github.com/inspec/inspec/pull/3346) ([jquick](https://github.com/jquick))
46
65
  - Fix profile vendoring on Windows [#3378](https://github.com/inspec/inspec/pull/3378) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
47
- <!-- latest_stable_release -->
48
66
 
49
67
  ## [v2.2.78](https://github.com/inspec/inspec/tree/v2.2.78) (2018-08-30)
50
68
 
data/docs/profiles.md CHANGED
@@ -348,6 +348,7 @@ Attributes may contain the following options:
348
348
 
349
349
 
350
350
  You can specify attributes in your `inspec.yml` using the `attributes` setting. For example, to add a `user` attribute for your profile:
351
+
351
352
  ```YAML
352
353
  attributes:
353
354
  - name: user
@@ -356,6 +357,7 @@ attributes:
356
357
  ```
357
358
 
358
359
  Example of adding a array object of servers:
360
+
359
361
  ```YAML
360
362
  attributes:
361
363
  - name: servers
@@ -369,6 +371,7 @@ attributes:
369
371
  To access an attribute you will use the `attribute` keyword. You can use this anywhere in your control code.
370
372
 
371
373
  For example:
374
+
372
375
  ```Ruby
373
376
  current_user = attribute('user')
374
377
 
@@ -386,6 +389,7 @@ end
386
389
  For sensitive data it is recomended to use a secrets YAML file located on the local machine to populate the values of attributes. A secrets file will always overwrite a attributes default value. To use the secrets file run `inspec exec` and specify the path to that Yaml file using the `--attrs` attribute.
387
390
 
388
391
  For example, a inspec.yml:
392
+
389
393
  ```YAML
390
394
  attributes:
391
395
  - name: username
@@ -432,6 +436,7 @@ $ inspec exec examples/profile-attribute --attrs examples/profile-attribute.yml
432
436
  To change your attributes for platform specific cases you can setup multiple `--attrs` files.
433
437
 
434
438
  For example, a inspec.yml:
439
+
435
440
  ```YAML
436
441
  attributes:
437
442
  - name: users
@@ -440,6 +445,7 @@ attributes:
440
445
  ```
441
446
 
442
447
  A YAML file named `windows.yml`
448
+
443
449
  ```YAML
444
450
  users:
445
451
  - Administrator
@@ -448,6 +454,7 @@ users:
448
454
  ```
449
455
 
450
456
  A YAML file named `linux.yml`
457
+
451
458
  ```YAML
452
459
  users:
453
460
  - root
@@ -456,6 +463,7 @@ users:
456
463
  ```
457
464
 
458
465
  The control file:
466
+
459
467
  ```RUBY
460
468
  control 'system-users' do
461
469
  impact 0.8
@@ -468,6 +476,7 @@ end
468
476
  ```
469
477
 
470
478
  The following command runs the tests and applies the attributes specified:
479
+
471
480
  ```bash
472
481
  $ inspec exec examples/profile-attribute --attrs examples/windows.yml
473
482
  $ inspec exec examples/profile-attribute --attrs examples/linux.yml
@@ -46,6 +46,15 @@ The following examples show how to use this InSpec audit resource.
46
46
  its('version') { should eq '0.33.0' }
47
47
  end
48
48
 
49
+ ### Verify that a particular version is installed when there are multiple versions installed
50
+
51
+ describe gem('rubocop') do
52
+ it { should be_installed }
53
+ its('versions') { should include /0.51.0/ }
54
+ its('versions.count') { should_not be > 3 }
55
+ end
56
+
57
+
49
58
  ### Verify that a gem package is not installed
50
59
 
51
60
  describe gem('rubocop') do
@@ -72,6 +81,21 @@ The following examples show how to use this InSpec audit resource.
72
81
 
73
82
  <br>
74
83
 
84
+ ## Properties
85
+
86
+ ### version (String)
87
+
88
+ The `version` property returns a string of the default version on the system:
89
+
90
+ its('version') { should eq '0.33.0' }
91
+
92
+ ### versions
93
+
94
+ The `versions` property returns an array of strings of all the versions of the gem installed on the system:
95
+
96
+ its('versions') { should include /0.33/ }
97
+
98
+
75
99
  ## Matchers
76
100
 
77
101
  For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
@@ -82,8 +106,3 @@ The `be_installed` matcher tests if the named Gem package is installed:
82
106
 
83
107
  it { should be_installed }
84
108
 
85
- ### version
86
-
87
- The `version` matcher tests if the named package version is on the system:
88
-
89
- its('version') { should eq '0.33.0' }
@@ -62,6 +62,14 @@ The following examples show how to use this InSpec audit resource.
62
62
  describe sql.query("SELECT SERVERPROPERTY('ProductVersion') as result").row(0).column('result') do
63
63
  its("value") { should cmp > '12.00.4457' }
64
64
  end
65
+
66
+ ### Test a specific database
67
+
68
+ sql = mssql_session(user: 'my_user', password: 'password', db_name: 'test')
69
+
70
+ describe sql.query("SELECT Name AS result FROM Product WHERE ProductID == 1").row(0).column('result') do
71
+ its("value") { should eq 'foo' }
72
+ end
65
73
 
66
74
  <br>
67
75
 
@@ -17,7 +17,14 @@ module Inspec::Plugin::V2
17
17
  determine_plugin_conf_file
18
18
  read_conf_file
19
19
  unpack_conf_file
20
+
21
+ # Old-style (v0, v1) co-distributed plugins were called 'bundles'
22
+ # and were located in lib/bundles
20
23
  detect_bundled_plugins unless options[:omit_bundles]
24
+
25
+ # New-style (v2) co-distributed plugins are in lib/plugins,
26
+ # and may be safely loaded
27
+ detect_core_plugins unless options[:omit_core_plugins]
21
28
  end
22
29
 
23
30
  def load_all
@@ -26,17 +33,20 @@ module Inspec::Plugin::V2
26
33
  # rubocop: disable Lint/RescueException
27
34
  begin
28
35
  # We could use require, but under testing, we need to repeatedly reload the same
29
- # plugin.
30
- if plugin_details.entry_point.include?('test/unit/mock/plugins')
31
- load plugin_details.entry_point + '.rb'
32
- else
36
+ # plugin. However, gems only work with require (rubygems dooes not overload `load`)
37
+ if plugin_details.installation_type == :gem
38
+ activate_managed_gems_for_plugin(plugin_name)
33
39
  require plugin_details.entry_point
40
+ else
41
+ load_path = plugin_details.entry_point
42
+ load_path += '.rb' unless plugin_details.entry_point.end_with?('.rb')
43
+ load load_path
34
44
  end
35
45
  plugin_details.loaded = true
36
46
  annotate_status_after_loading(plugin_name)
37
47
  rescue ::Exception => ex
38
48
  plugin_details.load_exception = ex
39
- Inspec::Log.error "Could not load plugin #{plugin_name}"
49
+ Inspec::Log.error "Could not load plugin #{plugin_name}: #{ex.message}"
40
50
  end
41
51
  # rubocop: enable Lint/RescueException
42
52
  end
@@ -60,7 +70,7 @@ module Inspec::Plugin::V2
60
70
  end
61
71
 
62
72
  def activate(plugin_type, hook_name)
63
- activator = registry.find_activators(plugin_type: plugin_type, activation_name: hook_name).first
73
+ activator = registry.find_activators(plugin_type: plugin_type, activator_name: hook_name).first
64
74
  # We want to capture literally any possible exception here, since we are storing them.
65
75
  # rubocop: disable Lint/RescueException
66
76
  begin
@@ -80,7 +90,7 @@ module Inspec::Plugin::V2
80
90
  next if act.activated
81
91
  # If there is anything in the CLI args with the same name, activate it
82
92
  # If the word 'help' appears in the first position, load all CLI plugins
83
- if cli_args.include?(act.activator_name.to_s) || cli_args[0] == 'help'
93
+ if cli_args.include?(act.activator_name.to_s) || cli_args[0] == 'help' || cli_args.size.zero?
84
94
  activate(:cli_command, act.activator_name)
85
95
  act.implementation_class.register_with_thor
86
96
  end
@@ -138,6 +148,22 @@ module Inspec::Plugin::V2
138
148
  @plugin_conf_file_path = File.join(@plugin_conf_file_path, 'plugins.json')
139
149
  end
140
150
 
151
+ def detect_core_plugins
152
+ core_plugins_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'plugins'))
153
+ # These are expected to be organized as proper separate projects,
154
+ # with lib/ dirs, etc.
155
+ Dir.glob(File.join(core_plugins_dir, 'inspec-*')).each do |plugin_dir|
156
+ status = Inspec::Plugin::V2::Status.new
157
+ status.name = File.basename(plugin_dir)
158
+ status.entry_point = File.join(plugin_dir, 'lib', status.name + '.rb')
159
+ status.installation_type = :path
160
+ status.loaded = false
161
+ registry[status.name.to_sym] = status
162
+ end
163
+ end
164
+
165
+ # TODO: DRY up re: Installer read_or_init_config_file
166
+ # TODO: refactor the plugin.json file to have its own class, which Loader consumes
141
167
  def read_conf_file
142
168
  if File.exist?(@plugin_conf_file_path)
143
169
  @plugin_file_contents = JSON.parse(File.read(@plugin_conf_file_path))
@@ -10,7 +10,7 @@ module Inspec::Reporters
10
10
  end
11
11
 
12
12
  def render
13
- output(report_merged.to_json, false)
13
+ output(report.to_json, false)
14
14
  end
15
15
 
16
16
  def report
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '2.2.102'
7
+ VERSION = '2.2.112'
8
8
  end
@@ -0,0 +1,16 @@
1
+ # Core Plugins of InSpec
2
+
3
+ This area contains the plugins that ship with InSpec. They are automatically detected by the plugin loader.
4
+
5
+ ## inspec-* directories
6
+
7
+ Each subdirectory here that begins with `inspec-` is intended to be a self-contained plugin project,
8
+ with code, docs, and tests.
9
+
10
+ ## shared directory
11
+
12
+ This directory contains material that is reusable for core plugins, such as a test helper tuned to assisting
13
+ core plugin testing.
14
+
15
+
16
+
@@ -0,0 +1,162 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'pathname'
4
+ require 'set'
5
+ require 'tempfile'
6
+ require 'yaml'
7
+
8
+ module InspecPlugins
9
+ module Artifact
10
+ class Base
11
+ KEY_BITS=2048
12
+ KEY_ALG=OpenSSL::PKey::RSA
13
+
14
+ INSPEC_PROFILE_VERSION_1='INSPEC-PROFILE-1'.freeze
15
+ INSPEC_REPORT_VERSION_1='INSPEC-REPORT-1'.freeze
16
+
17
+ ARTIFACT_DIGEST=OpenSSL::Digest::SHA512
18
+ ARTIFACT_DIGEST_NAME='SHA512'.freeze
19
+
20
+ VALID_PROFILE_VERSIONS=Set.new [INSPEC_PROFILE_VERSION_1]
21
+ VALID_PROFILE_DIGESTS=Set.new [ARTIFACT_DIGEST_NAME]
22
+
23
+ SIGNED_PROFILE_SUFFIX='iaf'.freeze
24
+ SIGNED_REPORT_SUFFIX='iar'.freeze
25
+
26
+ def self.keygen(options)
27
+ key = KEY_ALG.new KEY_BITS
28
+ puts 'Generating private key'
29
+ open "#{options['keyname']}.pem.key", 'w' do |io| io.write key.to_pem end
30
+ puts 'Generating public key'
31
+ open "#{options['keyname']}.pem.pub", 'w' do |io| io.write key.public_key.to_pem end
32
+ end
33
+
34
+ def self.profile_sign(options)
35
+ artifact = new
36
+ Dir.mktmpdir do |workdir|
37
+ puts "Signing #{options['profile']} with key #{options['keyname']}"
38
+ path_to_profile = options['profile']
39
+ profile_md = artifact.read_profile_metadata(path_to_profile)
40
+ artifact_filename = "#{profile_md['name']}-#{profile_md['version']}.#{SIGNED_PROFILE_SUFFIX}"
41
+ tarfile = artifact.profile_compress(path_to_profile, profile_md, workdir)
42
+ content = IO.binread(tarfile)
43
+ signing_key = KEY_ALG.new File.read "#{options['keyname']}.pem.key"
44
+ sha = ARTIFACT_DIGEST.new
45
+ signature = signing_key.sign sha, content
46
+ # convert the signature to Base64
47
+ signature_base64 = Base64.encode64(signature)
48
+ tar_content = IO.binread(tarfile)
49
+ File.open(artifact_filename, 'wb') do |f|
50
+ f.puts(INSPEC_PROFILE_VERSION_1)
51
+ f.puts(options['keyname'])
52
+ f.puts(ARTIFACT_DIGEST_NAME)
53
+ f.puts(signature_base64)
54
+ f.puts('') # newline separates artifact header with body
55
+ f.write(tar_content)
56
+ end
57
+ puts "Successfully generated #{artifact_filename}"
58
+ end
59
+ end
60
+
61
+ def self.profile_verify(options)
62
+ artifact = new
63
+ file_to_verifiy = options['infile']
64
+ puts "Verifying #{file_to_verifiy}"
65
+ artifact.verify(file_to_verifiy) do ||
66
+ puts 'Artifact is valid'
67
+ end
68
+ end
69
+
70
+ def self.profile_install(options)
71
+ artifact = new
72
+ puts 'Installing profile'
73
+ file_to_verifiy = options['infile']
74
+ dest_dir = options['destdir']
75
+ artifact.verify(file_to_verifiy) do |content|
76
+ Dir.mktmpdir do |workdir|
77
+ tmpfile = Pathname.new(workdir).join('artifact_to_install.tar.gz')
78
+ File.write(tmpfile, content)
79
+ puts "Installing to #{dest_dir}"
80
+ `tar xzf #{tmpfile} -C #{dest_dir}`
81
+ end
82
+ end
83
+ end
84
+
85
+ def read_profile_metadata(path_to_profile)
86
+ begin
87
+ p = Pathname.new(path_to_profile)
88
+ p = p.join('inspec.yml')
89
+ if not p.exist?
90
+ raise "#{path_to_profile} doesn't appear to be a valid Inspec profile"
91
+ end
92
+ yaml = YAML.load_file(p.to_s)
93
+ yaml = yaml.to_hash
94
+
95
+ if not yaml.key? 'name'
96
+ raise 'Profile is invalid, name is not defined'
97
+ end
98
+
99
+ if not yaml.key? 'version'
100
+ raise 'Profile is invalid, version is not defined'
101
+ end
102
+ rescue => e
103
+ # rewrap it and pass it up to the CLI
104
+ raise "Error reading Inspec profile metadata: #{e}"
105
+ end
106
+
107
+ yaml
108
+ end
109
+
110
+ def profile_compress(path_to_profile, profile_md, workdir)
111
+ profile_name = profile_md['name']
112
+ profile_version = profile_md['version']
113
+ outfile_name = "#{workdir}/#{profile_name}-#{profile_version}.tar.gz"
114
+ `tar czf #{outfile_name} -C #{path_to_profile} .`
115
+ outfile_name
116
+ end
117
+
118
+ def valid_header?(file_alg, file_version, file_keyname)
119
+ public_keyfile = "#{file_keyname}.pem.pub"
120
+ puts "Looking for #{public_keyfile} to verify artifact"
121
+ if !File.exist? public_keyfile
122
+ raise "Can't find #{public_keyfile}"
123
+ end
124
+
125
+ raise 'Invalid artifact digest algorithm detected' if !VALID_PROFILE_DIGESTS.member?(file_alg)
126
+ raise 'Invalid artifact version detected' if !VALID_PROFILE_VERSIONS.member?(file_version)
127
+ end
128
+
129
+ def verify(file_to_verifiy, &content_block)
130
+ f = File.open(file_to_verifiy, 'r')
131
+ file_version = f.readline.strip!
132
+ file_keyname = f.readline.strip!
133
+ file_alg = f.readline.strip!
134
+
135
+ file_sig = ''
136
+ # the signature is multi-line
137
+ while (line = f.readline) != "\n"
138
+ file_sig += line
139
+ end
140
+ file_sig.strip!
141
+ f.close
142
+
143
+ valid_header?(file_alg, file_version, file_keyname)
144
+
145
+ public_keyfile = "#{file_keyname}.pem.pub"
146
+ verification_key = KEY_ALG.new File.read public_keyfile
147
+
148
+ f = File.open(file_to_verifiy, 'r')
149
+ while f.readline != "\n" do end
150
+ content = f.read
151
+
152
+ signature = Base64.decode64(file_sig)
153
+ digest = ARTIFACT_DIGEST.new
154
+ if verification_key.verify digest, signature, content
155
+ content_block.yield(content)
156
+ else
157
+ puts 'Artifact is invalid'
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require_relative 'base'
3
+
4
+ #
5
+ # Notes:
6
+ #
7
+ # Generate keys
8
+ # The initial implementation uses 2048 bit RSA key pairs (public + private).
9
+ # Public keys must be available for a customer to install and verify an artifact.
10
+ # Private keys should be stored in a secure location and NOT be distributed.
11
+ # (They're only for creating artifacts).
12
+ #
13
+ #
14
+ # .IAF file format
15
+ # .iaf = "Inspec Artifact File", easy to rename if you'd like something more appropriate.
16
+ # The iaf file wraps a binary artifact with some metadata. The first implementation
17
+ # looks like this:
18
+ #
19
+ # INSPEC-PROFILE-1
20
+ # name_of_signing_key
21
+ # algorithm
22
+ # signature
23
+ # <empty line>
24
+ # binary-blob
25
+ # <eof>
26
+ #
27
+ # Let's look at each line:
28
+ # INSPEC-PROFILE-1:
29
+ # This is the artifact version descriptor. It should't change unless the
30
+ # format of the archive changes.
31
+ #
32
+ # name_of_signing_key
33
+ # The name of the public key that can be used to verify an artifact
34
+ #
35
+ # algorithm
36
+ # The digest used to sign, I picked SHA512 to start with.
37
+ # If we support multiple digests, we'll need to have the verify() method
38
+ # support each digest.
39
+ #
40
+ # signature
41
+ # The result of passing the binary artifact through the digest algorithm above.
42
+ # Result is base64 encoded.
43
+ #
44
+ # <empty line>
45
+ # We use an empty line to separate artifact header from artifact body (binary blob).
46
+ # The artifact body can be anything you like.
47
+ #
48
+ # binary-blob
49
+ # A binary blob, most likely a .tar.gz or tar.xz file. We'll need to pick one and
50
+ # stick with it as part of the "INSPEC-PROFILE-1" artifact version. If we change block
51
+ # format, the artifact version descriptor must be incremented, and the sign()
52
+ # and verify() methods must be updated to support a newer version.
53
+ #
54
+ #
55
+ # Key revocation
56
+ # This implementation doesn't support key revocation. However, a customer
57
+ # can remove the public cert file before installation, and artifacts will then
58
+ # fail verification.
59
+ #
60
+ # Key locations
61
+ # This implementation uses the current working directory to find public and
62
+ # private keys. We should establish a common key directory (similar to /hab/cache/keys
63
+ # or ~/.hab/cache/keys in Habitat).
64
+ #
65
+ # Extracting artifacts outside of Inspec
66
+ # As in Habitat, the artifact format for Inspec allows the use of common
67
+ # Unix tools to read the header and body of an artifact.
68
+ # To extract the header from a .iaf:
69
+ # sed '/^$/q' foo.iaf
70
+ # To extract the raw content from a .iaf:
71
+ # sed '1,/^$/d' foo.iaf
72
+
73
+ module InspecPlugins
74
+ module Artifact
75
+ class CLI < Inspec.plugin(2, :cli_command)
76
+ subcommand_desc 'artifact SUBCOMMAND', 'Manage InSpec Artifacts'
77
+
78
+ desc 'generate', 'Generate a RSA key pair for signing and verification'
79
+ option :keyname, type: :string, required: true,
80
+ desc: 'Desriptive name of key'
81
+ option :keydir, type: :string, default: './',
82
+ desc: 'Directory to search for keys'
83
+ def generate_keys
84
+ puts 'Generating keys'
85
+ InspecPlugins::Artifact::Base.keygen(options)
86
+ end
87
+
88
+ desc 'sign-profile', 'Create a signed .iaf artifact'
89
+ option :profile, type: :string, required: true,
90
+ desc: 'Path to profile directory'
91
+ option :keyname, type: :string, required: true,
92
+ desc: 'Desriptive name of key'
93
+ def sign_profile
94
+ InspecPlugins::Artifact::Base.profile_sign(options)
95
+ end
96
+
97
+ desc 'verify-profile', 'Verify a signed .iaf artifact'
98
+ option :infile, type: :string, required: true,
99
+ desc: '.iaf file to verify'
100
+ def verify_profile
101
+ InspecPlugins::Artifact::Base.profile_verify(options)
102
+ end
103
+
104
+ desc 'install-profile', 'Verify and install a signed .iaf artifact'
105
+ option :infile, type: :string, required: true,
106
+ desc: '.iaf file to install'
107
+ option :destdir, type: :string, required: true,
108
+ desc: 'Installation directory'
109
+ def install_profile
110
+ InspecPlugins::Artifact::Base.profile_install(options)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,12 @@
1
+ module InspecPlugins
2
+ module Artifact
3
+ class Plugin < Inspec.plugin(2)
4
+ plugin_name :'inspec-artifact'
5
+
6
+ cli_command :artifact do
7
+ require_relative 'inspec-artifact/cli'
8
+ InspecPlugins::Artifact::CLI
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../../../shared/core_plugin_test_helper.rb'
4
+ require 'fileutils'
5
+ require 'securerandom'
6
+
7
+ class ArtifactCli < MiniTest::Test
8
+ include CorePluginFunctionalHelper
9
+
10
+ def test_generating_archive_keys
11
+ Dir.mktmpdir do |dir|
12
+ unique_key_name = SecureRandom.uuid()
13
+ out = run_inspec_process("artifact generate --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
14
+ assert_equal 0, out.exit_status
15
+
16
+ stdout = out.stdout.force_encoding(Encoding::UTF_8)
17
+ assert_includes stdout, 'Generating private key'
18
+ assert_includes stdout, 'Generating public key'
19
+ end
20
+ end
21
+
22
+ def test_verify_and_install_signed_profile
23
+ Dir.mktmpdir do |dir|
24
+ unique_key_name = SecureRandom.uuid()
25
+ install_dir = File.join(dir, SecureRandom.uuid())
26
+ profile = File.join(dir, 'profile')
27
+ FileUtils.mkdir(install_dir)
28
+
29
+ # create profile
30
+ profile = File.join(dir, 'artifact-profile')
31
+ run_inspec_process("init profile artifact-profile", prefix: "cd #{dir} &&")
32
+
33
+ out = run_inspec_process("artifact generate --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
34
+ assert_equal 0, out.exit_status
35
+
36
+ out = run_inspec_process("artifact sign-profile --profile #{profile} --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
37
+ assert_equal 0, out.exit_status
38
+
39
+ out = run_inspec_process("artifact install-profile --infile artifact-profile-0.1.0.iaf --destdir #{install_dir}", prefix: "cd #{dir} &&")
40
+ assert_equal 0, out.exit_status
41
+
42
+ assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Installing to #{install_dir}"
43
+ assert_includes Dir.entries(install_dir).join, 'inspec.yml'
44
+ end
45
+ end
46
+ end