inspec-core 2.2.102 → 2.2.112

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