chef 16.7.61 → 16.9.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -5
  3. data/README.md +2 -2
  4. data/chef.gemspec +12 -2
  5. data/lib/chef/application/base.rb +1 -1
  6. data/lib/chef/client.rb +3 -0
  7. data/lib/chef/compliance/default_attributes.rb +93 -0
  8. data/lib/chef/compliance/fetcher/automate.rb +69 -0
  9. data/lib/chef/compliance/fetcher/chef_server.rb +134 -0
  10. data/lib/chef/compliance/reporter/automate.rb +201 -0
  11. data/lib/chef/compliance/reporter/chef_server_automate.rb +94 -0
  12. data/lib/chef/compliance/reporter/compliance_enforcer.rb +20 -0
  13. data/lib/chef/compliance/reporter/json_file.rb +19 -0
  14. data/lib/chef/compliance/runner.rb +262 -0
  15. data/lib/chef/cookbook_manifest.rb +1 -0
  16. data/lib/chef/encrypted_data_bag_item/assertions.rb +1 -1
  17. data/lib/chef/exceptions.rb +4 -0
  18. data/lib/chef/http/ssl_policies.rb +33 -14
  19. data/lib/chef/knife/bootstrap/train_connector.rb +1 -1
  20. data/lib/chef/knife/core/formatting_options.rb +49 -0
  21. data/lib/chef/knife/core/node_presenter.rb +0 -25
  22. data/lib/chef/knife/core/status_presenter.rb +1 -26
  23. data/lib/chef/knife/core/ui.rb +4 -1
  24. data/lib/chef/knife/core/windows_bootstrap_context.rb +1 -1
  25. data/lib/chef/knife/node_show.rb +2 -1
  26. data/lib/chef/knife/search.rb +2 -1
  27. data/lib/chef/knife/ssh.rb +3 -1
  28. data/lib/chef/knife/status.rb +8 -11
  29. data/lib/chef/mixin/powershell_exec.rb +3 -1
  30. data/lib/chef/platform/query_helpers.rb +4 -4
  31. data/lib/chef/policy_builder/policyfile.rb +1 -1
  32. data/lib/chef/powershell.rb +2 -0
  33. data/lib/chef/provider/dsc_resource.rb +12 -24
  34. data/lib/chef/provider/dsc_script.rb +16 -20
  35. data/lib/chef/provider/git.rb +5 -5
  36. data/lib/chef/provider/package.rb +53 -19
  37. data/lib/chef/provider/package/dnf.rb +39 -12
  38. data/lib/chef/provider/package/dnf/dnf_helper.py +18 -5
  39. data/lib/chef/provider/package/dnf/python_helper.rb +6 -6
  40. data/lib/chef/provider/package/freebsd/pkgng.rb +3 -1
  41. data/lib/chef/provider/yum_repository.rb +2 -2
  42. data/lib/chef/resource/chef_client_config.rb +1 -1
  43. data/lib/chef/resource/chef_gem.rb +2 -2
  44. data/lib/chef/resource/cron/cron_d.rb +1 -0
  45. data/lib/chef/resource/dsc_script.rb +8 -1
  46. data/lib/chef/resource/file.rb +1 -1
  47. data/lib/chef/resource/gem_package.rb +2 -2
  48. data/lib/chef/resource/homebrew_cask.rb +3 -3
  49. data/lib/chef/resource/hostname.rb +3 -3
  50. data/lib/chef/resource/http_request.rb +1 -1
  51. data/lib/chef/resource/locale.rb +1 -1
  52. data/lib/chef/resource/mdadm.rb +2 -2
  53. data/lib/chef/resource/osx_profile.rb +7 -7
  54. data/lib/chef/resource/remote_directory.rb +1 -1
  55. data/lib/chef/resource/ruby.rb +1 -5
  56. data/lib/chef/resource/ruby_block.rb +1 -1
  57. data/lib/chef/resource/template.rb +2 -2
  58. data/lib/chef/resource/user/windows_user.rb +5 -0
  59. data/lib/chef/resource/windows_certificate.rb +9 -13
  60. data/lib/chef/resource/yum_repository.rb +5 -0
  61. data/lib/chef/resource_collection/resource_set.rb +1 -1
  62. data/lib/chef/util/dsc/configuration_generator.rb +52 -11
  63. data/lib/chef/util/dsc/lcm_output_parser.rb +3 -4
  64. data/lib/chef/util/dsc/local_configuration_manager.rb +17 -14
  65. data/lib/chef/util/dsc/resource_store.rb +5 -11
  66. data/lib/chef/version.rb +1 -1
  67. data/lib/chef/win32/api/file.rb +4 -0
  68. data/spec/data/rubygems.org/latest_specs.4.8.gz +0 -0
  69. data/spec/data/rubygems.org/nonexistent_gem +0 -0
  70. data/spec/data/rubygems.org/sexp_processor +0 -0
  71. data/spec/data/rubygems.org/sexp_processor-4.15.1.gemspec.rz +0 -0
  72. data/spec/data/ssl/binary/chef-rspec-der.cert +0 -0
  73. data/spec/data/ssl/binary/chef-rspec-der.key +0 -0
  74. data/spec/functional/resource/dnf_package_spec.rb +319 -16
  75. data/spec/functional/resource/dsc_script_spec.rb +3 -6
  76. data/spec/functional/resource/windows_certificate_spec.rb +204 -384
  77. data/spec/integration/client/client_spec.rb +2 -1
  78. data/spec/integration/compliance/compliance_spec.rb +81 -0
  79. data/spec/integration/recipes/recipe_dsl_spec.rb +1 -0
  80. data/spec/spec_helper.rb +1 -1
  81. data/spec/unit/client_spec.rb +1 -0
  82. data/spec/unit/compliance/fetcher/automate_spec.rb +134 -0
  83. data/spec/unit/compliance/fetcher/chef_server_spec.rb +93 -0
  84. data/spec/unit/compliance/reporter/automate_spec.rb +427 -0
  85. data/spec/unit/compliance/reporter/chef_server_automate_spec.rb +177 -0
  86. data/spec/unit/compliance/reporter/compliance_enforcer_spec.rb +48 -0
  87. data/spec/unit/compliance/runner_spec.rb +167 -0
  88. data/spec/unit/http/ssl_policies_spec.rb +107 -68
  89. data/spec/unit/knife/bootstrap_spec.rb +5 -17
  90. data/spec/unit/knife/core/node_editor_spec.rb +1 -1
  91. data/spec/unit/knife/core/status_presenter_spec.rb +54 -0
  92. data/spec/unit/mixin/openssl_helper_spec.rb +0 -7
  93. data/spec/unit/mixin/powershell_exec_spec.rb +1 -1
  94. data/spec/unit/platform/query_helpers_spec.rb +11 -12
  95. data/spec/unit/provider/dsc_resource_spec.rb +10 -27
  96. data/spec/unit/provider/dsc_script_spec.rb +1 -1
  97. data/spec/unit/provider/mount/windows_spec.rb +1 -0
  98. data/spec/unit/provider/package/freebsd/pkgng_spec.rb +1 -1
  99. data/spec/unit/provider/package/rubygems_spec.rb +39 -7
  100. data/spec/unit/provider/systemd_unit_spec.rb +1 -1
  101. data/spec/unit/resource/user/windows_user_spec.rb +36 -0
  102. data/spec/unit/resource/windows_certificate_spec.rb +12 -0
  103. data/spec/unit/util/dsc/configuration_generator_spec.rb +79 -0
  104. data/spec/unit/util/dsc/local_configuration_manager_spec.rb +27 -35
  105. metadata +55 -18
  106. data/lib/chef/util/powershell/cmdlet.rb +0 -169
  107. data/lib/chef/util/powershell/cmdlet_result.rb +0 -61
  108. data/spec/data/trusted_certs_empty/.gitkeep +0 -0
  109. data/spec/data/trusted_certs_empty/README.md +0 -1
  110. data/spec/functional/util/powershell/cmdlet_spec.rb +0 -111
  111. data/spec/scripts/ssl-serve.rb +0 -47
  112. data/spec/unit/util/powershell/cmdlet_spec.rb +0 -106
@@ -97,7 +97,8 @@ describe "chef-client" do
97
97
  before { file ".chef/knife.rb", "xxx.xxx" }
98
98
 
99
99
  it "should load .chef/knife.rb when -z is specified" do
100
- result = shell_out("#{chef_client} -z -o 'x::default'", cwd: path_to(""))
100
+ # On Solaris shell_out will invoke /bin/sh which doesn't understand how to correctly update ENV['PWD']
101
+ result = shell_out("#{chef_client} -z -o 'x::default'", cwd: path_to(""), env: { "PWD" => nil })
101
102
  # FATAL: Configuration error NoMethodError: undefined method `xxx' for nil:NilClass
102
103
  expect(result.stdout).to include("xxx")
103
104
  end
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ require "support/shared/integration/integration_helper"
4
+ require "chef/mixin/shell_out"
5
+ require "chef-utils/dist"
6
+
7
+ describe "chef-client with audit mode" do
8
+
9
+ include IntegrationSupport
10
+ include Chef::Mixin::ShellOut
11
+
12
+ let(:chef_dir) { File.join(__dir__, "..", "..", "..", "bin") }
13
+
14
+ # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
15
+ # following constraints are satisfied:
16
+ # * Windows: windows can only run batch scripts as bare executables. Rubygems
17
+ # creates batch wrappers for installed gems, but we don't have batch wrappers
18
+ # in the source tree.
19
+ # * Other `chef-client` in PATH: A common case is running the tests on a
20
+ # machine that has omnibus chef installed. In that case we need to ensure
21
+ # we're running `chef-client` from the source tree and not the external one.
22
+ # cf. CHEF-4914
23
+ let(:chef_client) { "bundle exec #{ChefUtils::Dist::Infra::CLIENT} --minimal-ohai" }
24
+
25
+ when_the_repository "has a custom profile" do
26
+ let(:report_file) { path_to("report_file.json") }
27
+
28
+ before do
29
+ directory "profiles/my-profile" do
30
+ file "inspec.yml", <<~FILE
31
+ ---
32
+ name: my-profile
33
+ FILE
34
+
35
+ directory "controls" do
36
+ file "my_control.rb", <<~FILE
37
+ control "my control" do
38
+ describe Dir.home do
39
+ it { should be_kind_of String }
40
+ end
41
+ end
42
+ FILE
43
+ end
44
+ end
45
+
46
+ file "attributes.json", <<~FILE
47
+ {
48
+ "audit": {
49
+ "json_file": {
50
+ "location": "#{report_file}"
51
+ },
52
+ "profiles": {
53
+ "my-profile": {
54
+ "path": "#{path_to("profiles/my-profile")}"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ FILE
60
+ end
61
+
62
+ it "should complete with success" do
63
+ result = shell_out!("#{chef_client} --local-mode --json-attributes #{path_to("attributes.json")}", cwd: chef_dir)
64
+ result.error!
65
+
66
+ inspec_report = JSON.parse(File.read(report_file))
67
+ expect(inspec_report["profiles"].length).to eq(1)
68
+
69
+ profile = inspec_report["profiles"].first
70
+ expect(profile["name"]).to eq("my-profile")
71
+ expect(profile["controls"].length).to eq(1)
72
+
73
+ control = profile["controls"].first
74
+ expect(control["id"]).to eq("my control")
75
+ expect(control["results"].length).to eq(1)
76
+
77
+ result = control["results"].first
78
+ expect(result["status"]).to eq("passed")
79
+ end
80
+ end
81
+ end
@@ -28,6 +28,7 @@ describe "Recipe DSL methods" do
28
28
  def provider
29
29
  Provider
30
30
  end
31
+
31
32
  class Provider < Chef::Provider
32
33
  def load_current_resource; end
33
34
 
@@ -31,7 +31,7 @@ $LOAD_PATH.unshift File.expand_path("../chef-utils/lib", __dir__)
31
31
 
32
32
  require "rubygems"
33
33
  require "rspec/mocks"
34
-
34
+ require "rexml/document"
35
35
  require "webmock/rspec"
36
36
 
37
37
  require "chef"
@@ -129,6 +129,7 @@ shared_context "a client run" do
129
129
  expect(client.events).to receive(:register).with(instance_of(Chef::DataCollector::Reporter))
130
130
  expect(client.events).to receive(:register).with(instance_of(Chef::ResourceReporter))
131
131
  expect(client.events).to receive(:register).with(instance_of(Chef::ActionCollection))
132
+ expect(client.events).to receive(:register).with(instance_of(Chef::Compliance::Runner))
132
133
  end
133
134
 
134
135
  def stub_for_node_load
@@ -0,0 +1,134 @@
1
+ require "spec_helper"
2
+ require "chef/compliance/fetcher/automate"
3
+
4
+ describe Chef::Compliance::Fetcher::Automate do
5
+ describe ".resolve" do
6
+ before do
7
+ Chef::Config[:data_collector] = {
8
+ server_url: "https://automate.test/data_collector",
9
+ token: token,
10
+ }
11
+ end
12
+
13
+ let(:token) { "fake_token" }
14
+
15
+ context "when target is a string" do
16
+ it "should resolve a compliance URL" do
17
+ res = Chef::Compliance::Fetcher::Automate.resolve("compliance://namespace/profile_name")
18
+
19
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
20
+ expected = "https://automate.test/compliance/profiles/namespace/profile_name/tar"
21
+ expect(res.target).to eq(expected)
22
+ end
23
+
24
+ it "raises an exception with no data collector token" do
25
+ Chef::Config[:data_collector].delete(:token)
26
+
27
+ expect {
28
+ Chef::Compliance::Fetcher::Automate.resolve("compliance://namespace/profile_name")
29
+ }.to raise_error(/No data-collector token set/)
30
+ end
31
+
32
+ it "includes the data collector token" do
33
+ expect(Chef::Compliance::Fetcher::Automate).to receive(:new).with(
34
+ "https://automate.test/compliance/profiles/namespace/profile_name/tar",
35
+ hash_including("token" => token)
36
+ ).and_call_original
37
+
38
+ res = Chef::Compliance::Fetcher::Automate.resolve("compliance://namespace/profile_name")
39
+
40
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
41
+ expected = "https://automate.test/compliance/profiles/namespace/profile_name/tar"
42
+ expect(res.target).to eq(expected)
43
+ end
44
+
45
+ it "returns nil with a non-compliance URL" do
46
+ res = Chef::Compliance::Fetcher::Automate.resolve("http://github.com/chef-cookbooks/audit")
47
+
48
+ expect(res).to eq(nil)
49
+ end
50
+ end
51
+
52
+ context "when target is a hash" do
53
+ it "should resolve a target with a version" do
54
+ res = Chef::Compliance::Fetcher::Automate.resolve(
55
+ compliance: "namespace/profile_name",
56
+ version: "1.2.3"
57
+ )
58
+
59
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
60
+ expected = "https://automate.test/compliance/profiles/namespace/profile_name/version/1.2.3/tar"
61
+ expect(res.target).to eq(expected)
62
+ end
63
+
64
+ it "should resolve a target without a version" do
65
+ res = Chef::Compliance::Fetcher::Automate.resolve(
66
+ compliance: "namespace/profile_name"
67
+ )
68
+
69
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
70
+ expected = "https://automate.test/compliance/profiles/namespace/profile_name/tar"
71
+ expect(res.target).to eq(expected)
72
+ end
73
+
74
+ it "uses url key when present" do
75
+ res = Chef::Compliance::Fetcher::Automate.resolve(
76
+ compliance: "namespace/profile_name",
77
+ version: "1.2.3",
78
+ url: "https://profile.server.test/profiles/profile_name/1.2.3"
79
+ )
80
+
81
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
82
+ expected = "https://profile.server.test/profiles/profile_name/1.2.3"
83
+ expect(res.target).to eq(expected)
84
+ end
85
+
86
+ it "does not include token in the config when url key is present" do
87
+ expect(Chef::Compliance::Fetcher::Automate).to receive(:new).with(
88
+ "https://profile.server.test/profiles/profile_name/1.2.3",
89
+ hash_including("token" => nil)
90
+ ).and_call_original
91
+
92
+ res = Chef::Compliance::Fetcher::Automate.resolve(
93
+ compliance: "namespace/profile_name",
94
+ version: "1.2.3",
95
+ url: "https://profile.server.test/profiles/profile_name/1.2.3"
96
+ )
97
+
98
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
99
+ expected = "https://profile.server.test/profiles/profile_name/1.2.3"
100
+ expect(res.target).to eq(expected)
101
+ end
102
+
103
+ it "raises an exception with no data collector token" do
104
+ Chef::Config[:data_collector].delete(:token)
105
+
106
+ expect {
107
+ Chef::Compliance::Fetcher::Automate.resolve(compliance: "namespace/profile_name")
108
+ }.to raise_error(Inspec::FetcherFailure, /No data-collector token set/)
109
+ end
110
+
111
+ it "includes the data collector token" do
112
+ expect(Chef::Compliance::Fetcher::Automate).to receive(:new).with(
113
+ "https://automate.test/compliance/profiles/namespace/profile_name/tar",
114
+ hash_including("token" => token)
115
+ ).and_call_original
116
+
117
+ res = Chef::Compliance::Fetcher::Automate.resolve(compliance: "namespace/profile_name")
118
+
119
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::Automate)
120
+ expected = "https://automate.test/compliance/profiles/namespace/profile_name/tar"
121
+ expect(res.target).to eq(expected)
122
+ end
123
+
124
+ it "returns nil with a non-profile Hash" do
125
+ res = Chef::Compliance::Fetcher::Automate.resolve(
126
+ profile: "namespace/profile_name",
127
+ version: "1.2.3"
128
+ )
129
+
130
+ expect(res).to eq(nil)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,93 @@
1
+ require "spec_helper"
2
+ require "chef/compliance/fetcher/chef_server"
3
+
4
+ describe Chef::Compliance::Fetcher::ChefServer do
5
+ let(:node) do
6
+ Chef::Node.new.tap do |n|
7
+ n.default["audit"] = {}
8
+ end
9
+ end
10
+
11
+ before :each do
12
+ allow(Chef).to receive(:node).and_return(node)
13
+
14
+ Chef::Config[:chef_server_url] = "http://127.0.0.1:8889/organizations/my_org"
15
+ end
16
+
17
+ describe ".resolve" do
18
+ context "when target is a string" do
19
+ it "should resolve a compliance URL" do
20
+ res = Chef::Compliance::Fetcher::ChefServer.resolve("compliance://namespace/profile_name")
21
+
22
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
23
+ expected = "http://127.0.0.1:8889/organizations/my_org/owners/namespace/compliance/profile_name/tar"
24
+ expect(res.target).to eq(expected)
25
+ end
26
+
27
+ it "should add /compliance URL prefix if needed" do
28
+ node.default["audit"]["fetcher"] = "chef-server"
29
+ res = Chef::Compliance::Fetcher::ChefServer.resolve("compliance://namespace/profile_name")
30
+
31
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
32
+ expected = "http://127.0.0.1:8889/compliance/organizations/my_org/owners/namespace/compliance/profile_name/tar"
33
+ expect(res.target).to eq(expected)
34
+ end
35
+
36
+ it "includes user in the URL if present" do
37
+ res = Chef::Compliance::Fetcher::ChefServer.resolve("compliance://username@namespace/profile_name")
38
+
39
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
40
+ expected = "http://127.0.0.1:8889/organizations/my_org/owners/username@namespace/compliance/profile_name/tar"
41
+ expect(res.target).to eq(expected)
42
+ end
43
+
44
+ it "returns nil with a non-compliance URL" do
45
+ res = Chef::Compliance::Fetcher::ChefServer.resolve("http://github.com/chef-cookbooks/audit")
46
+
47
+ expect(res).to eq(nil)
48
+ end
49
+ end
50
+
51
+ context "when target is a hash" do
52
+ it "should resolve a target with a version" do
53
+ res = Chef::Compliance::Fetcher::ChefServer.resolve(
54
+ compliance: "namespace/profile_name",
55
+ version: "1.2.3"
56
+ )
57
+
58
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
59
+ expected = "http://127.0.0.1:8889/organizations/my_org/owners/namespace/compliance/profile_name/version/1.2.3/tar"
60
+ expect(res.target).to eq(expected)
61
+ end
62
+
63
+ it "should resolve a target without a version" do
64
+ res = Chef::Compliance::Fetcher::ChefServer.resolve(
65
+ compliance: "namespace/profile_name"
66
+ )
67
+
68
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
69
+ expected = "http://127.0.0.1:8889/organizations/my_org/owners/namespace/compliance/profile_name/tar"
70
+ expect(res.target).to eq(expected)
71
+ end
72
+
73
+ it "includes user in the URL if present" do
74
+ res = Chef::Compliance::Fetcher::ChefServer.resolve(
75
+ compliance: "username@namespace/profile_name"
76
+ )
77
+
78
+ expect(res).to be_kind_of(Chef::Compliance::Fetcher::ChefServer)
79
+ expected = "http://127.0.0.1:8889/organizations/my_org/owners/username@namespace/compliance/profile_name/tar"
80
+ expect(res.target).to eq(expected)
81
+ end
82
+
83
+ it "returns nil with a non-profile Hash" do
84
+ res = Chef::Compliance::Fetcher::ChefServer.resolve(
85
+ profile: "namespace/profile_name",
86
+ version: "1.2.3"
87
+ )
88
+
89
+ expect(res).to eq(nil)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,427 @@
1
+ require "spec_helper"
2
+ require "json" # For .to_json
3
+
4
+ describe Chef::Compliance::Reporter::Automate do
5
+ let(:reporter) { Chef::Compliance::Reporter::Automate.new(opts) }
6
+
7
+ let(:opts) do
8
+ {
9
+ entity_uuid: "aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
10
+ run_id: "3f0536f7-3361-4bca-ae53-b45118dceb5d",
11
+ node_info: {
12
+ node: "chef-client.solo",
13
+ environment: "My Prod Env",
14
+ roles: %w{base_linux apache_linux},
15
+ recipes: ["some_cookbook::some_recipe", "some_cookbook"],
16
+ policy_name: "test_policy_name",
17
+ policy_group: "test_policy_group",
18
+ chef_tags: ["mylinux", "my.tag", "some=tag"],
19
+ organization_name: "test_org",
20
+ source_fqdn: "api.chef.io",
21
+ ipaddress: "192.168.56.33",
22
+ fqdn: "lb1.prod.example.com",
23
+ },
24
+ run_time_limit: 1.1,
25
+ control_results_limit: 2,
26
+ timestamp: Time.parse("2016-07-19T19:19:19+01:00"),
27
+ }
28
+ end
29
+
30
+ let(:inspec_report) do
31
+ {
32
+ "version": "1.2.1",
33
+ "profiles":
34
+ [{ "name": "tmp_compliance_profile",
35
+ "title": "/tmp Compliance Profile",
36
+ "summary": "An Example Compliance Profile",
37
+ "sha256": "7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd",
38
+ "version": "0.1.1",
39
+ "maintainer": "Nathen Harvey <nharvey@chef.io>",
40
+ "license": "Apache 2.0 License",
41
+ "copyright": "Nathen Harvey <nharvey@chef.io>",
42
+ "supports": [],
43
+ "controls":
44
+ [{ "title": "A /tmp directory must exist",
45
+ "desc": "A /tmp directory must exist",
46
+ "impact": 0.3,
47
+ "refs": [],
48
+ "tags": {},
49
+ "code": "control 'tmp-1.0' do\n impact 0.3\n title 'A /tmp directory must exist'\n desc 'A /tmp directory must exist'\n describe file '/tmp' do\n it { should be_directory }\n end\nend\n",
50
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 3 },
51
+ "id": "tmp-1.0",
52
+ "results": [
53
+ { "status": "passed", "code_desc": "File /tmp should be directory", "run_time": 0.002312, "start_time": "2016-10-19 11:09:43 -0400" },
54
+ ],
55
+ },
56
+ { "title": "/tmp directory is owned by the root user",
57
+ "desc": "The /tmp directory must be owned by the root user",
58
+ "impact": 0.3,
59
+ "refs": [{ "url": "https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf", "ref": "Compliance Whitepaper" }],
60
+ "tags": { "production": nil, "development": nil, "identifier": "value", "remediation": "https://github.com/chef-cookbooks/audit" },
61
+ "code": "control 'tmp-1.1' do\n impact 0.3\n title '/tmp directory is owned by the root user'\n desc 'The /tmp directory must be owned by the root user'\n tag 'production','development'\n tag identifier: 'value'\n tag remediation: 'https://github.com/chef-cookbooks/audit'\n ref 'Compliance Whitepaper', url: 'https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf'\n describe file '/tmp' do\n it { should be_owned_by 'root' }\n end\nend\n",
62
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 12 },
63
+ "id": "tmp-1.1",
64
+ "results": [
65
+ { "status": "passed", "code_desc": 'File /tmp should be owned by "root"', "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400" },
66
+ { "status": "skipped", "code_desc": 'File /tmp should be owned by "root"', "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400" },
67
+ { "status": "failed", "code_desc": "File /etc/hosts is expected to be directory", "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400", "message": "expected `File /etc/hosts.directory?` to return true, got false" },
68
+ ],
69
+ },
70
+ ],
71
+ "groups": [{ "title": "/tmp Compliance Profile", "controls": ["tmp-1.0", "tmp-1.1"], "id": "controls/tmp.rb" }],
72
+ "attributes": [{ "name": "syslog_pkg", "options": { "default": "rsyslog", "description": "syslog package..." } }] }],
73
+ "other_checks": [],
74
+ "statistics": { "duration": 0.032332 },
75
+ }
76
+ end
77
+
78
+ describe "#send_report" do
79
+ before :each do
80
+ WebMock.disable_net_connect!
81
+
82
+ Chef::Config[:data_collector] = { token: token, server_url: "https://automate.test/data_collector" }
83
+ end
84
+
85
+ let(:token) { "fake_token" }
86
+
87
+ it "sends report successfully to ChefAutomate with missing profiles" do
88
+ metasearch_stub = stub_request(:post, "https://automate.test/compliance/profiles/metasearch")
89
+ .with(
90
+ body: '{"sha256": ["7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd"]}',
91
+ headers: {
92
+ "Accept-Encoding" => "identity",
93
+ "X-Chef-Version" => Chef::VERSION,
94
+ "X-Data-Collector-Auth" => "version=1.0",
95
+ "X-Data-Collector-Token" => token,
96
+ }
97
+ ).to_return(
98
+ status: 200,
99
+ body: '{"missing_sha256": ["7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd"]}'
100
+ )
101
+
102
+ report_stub = stub_request(:post, "https://automate.test/data_collector")
103
+ .with(
104
+ body: {
105
+ "version": "1.2.1",
106
+ "profiles": [
107
+ {
108
+ "name": "tmp_compliance_profile",
109
+ "title": "/tmp Compliance Profile",
110
+ "summary": "An Example Compliance Profile",
111
+ "sha256": "7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd",
112
+ "version": "0.1.1",
113
+ "maintainer": "Nathen Harvey <nharvey@chef.io>",
114
+ "license": "Apache 2.0 License",
115
+ "copyright": "Nathen Harvey <nharvey@chef.io>",
116
+ "supports": [],
117
+ "controls": [
118
+ {
119
+ "title": "A /tmp directory must exist",
120
+ "desc": "A /tmp directory must exist",
121
+ "impact": 0.3,
122
+ "refs": [],
123
+ "tags": {},
124
+ "code": "control 'tmp-1.0' do\n impact 0.3\n title 'A /tmp directory must exist'\n desc 'A /tmp directory must exist'\n describe file '/tmp' do\n it { should be_directory }\n end\nend\n",
125
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 3 },
126
+ "id": "tmp-1.0",
127
+ "results": [
128
+ { "status": "passed", "code_desc": "File /tmp should be directory", "run_time": 0.002312, "start_time": "2016-10-19 11:09:43 -0400" },
129
+ ],
130
+ },
131
+ {
132
+ "title": "/tmp directory is owned by the root user",
133
+ "desc": "The /tmp directory must be owned by the root user",
134
+ "impact": 0.3,
135
+ "refs": [
136
+ { "url": "https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf", "ref": "Compliance Whitepaper" },
137
+ ],
138
+ "tags": { "production": nil, "development": nil, "identifier": "value", "remediation": "https://github.com/chef-cookbooks/audit" },
139
+ "code": "control 'tmp-1.1' do\n impact 0.3\n title '/tmp directory is owned by the root user'\n desc 'The /tmp directory must be owned by the root user'\n tag 'production','development'\n tag identifier: 'value'\n tag remediation: 'https://github.com/chef-cookbooks/audit'\n ref 'Compliance Whitepaper', url: 'https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf'\n describe file '/tmp' do\n it { should be_owned_by 'root' }\n end\nend\n",
140
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 12 },
141
+ "id": "tmp-1.1",
142
+ "results": [
143
+ { "status": "failed", "code_desc": "File /etc/hosts is expected to be directory", "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400", "message": "expected `File /etc/hosts.directory?` to return true, got false" },
144
+ { "status": "skipped", "code_desc": 'File /tmp should be owned by "root"', "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400" },
145
+ ],
146
+ "removed_results_counts": { "failed": 0, "skipped": 0, "passed": 1 },
147
+ },
148
+ ],
149
+ "groups": [
150
+ { "title": "/tmp Compliance Profile", "controls": ["tmp-1.0", "tmp-1.1"], "id": "controls/tmp.rb" },
151
+ ],
152
+ "attributes": [
153
+ { "name": "syslog_pkg", "options": { "default": "rsyslog", "description": "syslog package..." } },
154
+ ],
155
+ },
156
+ ],
157
+ "other_checks": [],
158
+ "statistics": { "duration": 0.032332 },
159
+ "type": "inspec_report",
160
+ "node_name": "chef-client.solo",
161
+ "end_time": "2016-07-19T18:19:19Z",
162
+ "node_uuid": "aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
163
+ "environment": "My Prod Env",
164
+ "roles": %w{base_linux apache_linux},
165
+ "recipes": ["some_cookbook::some_recipe", "some_cookbook"],
166
+ "report_uuid": "3f0536f7-3361-4bca-ae53-b45118dceb5d",
167
+ "source_fqdn": "api.chef.io",
168
+ "organization_name": "test_org",
169
+ "policy_group": "test_policy_group",
170
+ "policy_name": "test_policy_name",
171
+ "chef_tags": ["mylinux", "my.tag", "some=tag"],
172
+ "ipaddress": "192.168.56.33",
173
+ "fqdn": "lb1.prod.example.com",
174
+ "run_time_limit": 1.1,
175
+ },
176
+ headers: {
177
+ "Accept-Encoding" => "identity",
178
+ "X-Chef-Version" => Chef::VERSION,
179
+ "X-Data-Collector-Auth" => "version=1.0",
180
+ "X-Data-Collector-Token" => token,
181
+ }
182
+ ).to_return(status: 200)
183
+
184
+ expect(reporter.send_report(inspec_report)).to eq(true)
185
+
186
+ expect(metasearch_stub).to have_been_requested
187
+ expect(report_stub).to have_been_requested
188
+ end
189
+
190
+ it "sends report successfully to ChefAutomate with seen profiles" do
191
+ metasearch_stub = stub_request(:post, "https://automate.test/compliance/profiles/metasearch")
192
+ .with(
193
+ body: '{"sha256": ["7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd"]}',
194
+ headers: {
195
+ "Accept-Encoding" => "identity",
196
+ "X-Chef-Version" => Chef::VERSION,
197
+ "X-Data-Collector-Auth" => "version=1.0",
198
+ "X-Data-Collector-Token" => token,
199
+ }
200
+ ).to_return(
201
+ status: 200,
202
+ body: '{"missing_sha256": []}'
203
+ )
204
+
205
+ report_stub = stub_request(:post, "https://automate.test/data_collector")
206
+ .with(
207
+ body: {
208
+ "version": "1.2.1",
209
+ "profiles": [
210
+ {
211
+ "title": "/tmp Compliance Profile",
212
+ "sha256": "7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd",
213
+ "version": "0.1.1",
214
+ "controls": [
215
+ {
216
+ "id": "tmp-1.0",
217
+ "results": [
218
+ { "status": "passed", "code_desc": "File /tmp should be directory" },
219
+ ],
220
+ },
221
+ {
222
+ "id": "tmp-1.1",
223
+ "results": [
224
+ { "status": "failed", "code_desc": "File /etc/hosts is expected to be directory", "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400", "message": "expected `File /etc/hosts.directory?` to return true, got false" },
225
+ { "status": "skipped", "code_desc": 'File /tmp should be owned by "root"', "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400" },
226
+ ],
227
+ "removed_results_counts": { "failed": 0, "skipped": 0, "passed": 1 },
228
+ },
229
+ ],
230
+ "attributes": [
231
+ { "name": "syslog_pkg", "options": { "default": "rsyslog", "description": "syslog package..." } },
232
+ ],
233
+ },
234
+ ],
235
+ "other_checks": [],
236
+ "statistics": { "duration": 0.032332 },
237
+ "type": "inspec_report",
238
+ "node_name": "chef-client.solo",
239
+ "end_time": "2016-07-19T18:19:19Z",
240
+ "node_uuid": "aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
241
+ "environment": "My Prod Env",
242
+ "roles": %w{base_linux apache_linux},
243
+ "recipes": ["some_cookbook::some_recipe", "some_cookbook"],
244
+ "report_uuid": "3f0536f7-3361-4bca-ae53-b45118dceb5d",
245
+ "source_fqdn": "api.chef.io",
246
+ "organization_name": "test_org",
247
+ "policy_group": "test_policy_group",
248
+ "policy_name": "test_policy_name",
249
+ "chef_tags": ["mylinux", "my.tag", "some=tag"],
250
+ "ipaddress": "192.168.56.33",
251
+ "fqdn": "lb1.prod.example.com",
252
+ "run_time_limit": 1.1,
253
+ },
254
+ headers: {
255
+ "Accept-Encoding" => "identity",
256
+ "X-Chef-Version" => Chef::VERSION,
257
+ "X-Data-Collector-Auth" => "version=1.0",
258
+ "X-Data-Collector-Token" => token,
259
+ }
260
+ ).to_return(status: 200)
261
+
262
+ expect(reporter.send_report(inspec_report)).to eq(true)
263
+
264
+ expect(metasearch_stub).to have_been_requested
265
+ expect(report_stub).to have_been_requested
266
+ end
267
+
268
+ it "does not send report when entity_uuid is missing" do
269
+ opts.delete(:entity_uuid)
270
+ reporter = Chef::Compliance::Reporter::Automate.new(opts)
271
+ expect(reporter.send_report(inspec_report)).to eq(false)
272
+ end
273
+ end
274
+
275
+ describe "#truncate_controls_results" do
276
+ let(:report) do
277
+ {
278
+ "version": "1.2.1",
279
+ "profiles":
280
+ [{ "name": "tmp_compliance_profile",
281
+ "title": "/tmp Compliance Profile",
282
+ "summary": "An Example Compliance Profile",
283
+ "sha256": "7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215ff",
284
+ "version": "0.1.1",
285
+ "maintainer": "Nathen Harvey <nharvey@chef.io>",
286
+ "license": "Apache 2.0 License",
287
+ "copyright": "Nathen Harvey <nharvey@chef.io>",
288
+ "supports": [],
289
+ "controls":
290
+ [{ "id": "tmp-2.0",
291
+ "title": "A bunch of directories must exist",
292
+ "desc": "A bunch of directories must exist for testing",
293
+ "impact": 0.3,
294
+ "refs": [],
295
+ "tags": {},
296
+ "code": "control 'tmp-2.0' do\n impact 0.3\n title 'A bunch of directories must exist'\n desc 'A bunch of directories must exist for testing'\n describe file '/tmp' do\n it { should be_directory }\n end\nend\n",
297
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 3 },
298
+ "results": [
299
+ { "status": "passed", "code_desc": "File /tmp should be directory", "run_time": 0.002312, "start_time": "2016-10-19 11:09:43 -0400" },
300
+ { "status": "passed", "code_desc": "File /etc should be directory", "run_time": 0.002314, "start_time": "2016-10-19 11:09:45 -0400" },
301
+ { "status": "passed", "code_desc": "File /opt should be directory", "run_time": 0.002315, "start_time": "2016-10-19 11:09:46 -0400" },
302
+ { "status": "skipped", "code_desc": "No-op", "run_time": 0.002316, "start_time": "2016-10-19 11:09:44 -0400", "skip_message": "4 testing" },
303
+ { "status": "skipped", "code_desc": "No-op", "run_time": 0.002317, "start_time": "2016-10-19 11:09:44 -0400", "skip_message": "4 testing" },
304
+ { "status": "skipped", "code_desc": "No-op", "run_time": 0.002318, "start_time": "2016-10-19 11:09:44 -0400", "skip_message": "4 testing" },
305
+ { "status": "failed", "code_desc": "File /etc/passwd should be directory", "run_time": 0.002313, "start_time": "2016-10-19 11:09:44 -0400" },
306
+ { "status": "failed", "code_desc": "File /etc/passwd should be directory", "run_time": 0.002313, "start_time": "2016-10-19 11:09:44 -0400" },
307
+ { "status": "failed", "code_desc": "File /etc/passwd should be directory", "run_time": 0.002313, "start_time": "2016-10-19 11:09:44 -0400" },
308
+ ],
309
+ },
310
+ { "id": "tmp-2.1",
311
+ "title": "/tmp directory is owned by the root user",
312
+ "desc": "The /tmp directory must be owned by the root user",
313
+ "impact": 0.3,
314
+ "refs": [{ "url": "https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf", "ref": "Compliance Whitepaper" }],
315
+ "tags": { "production": nil, "development": nil, "identifier": "value", "remediation": "https://github.com/chef-cookbooks/audit" },
316
+ "code": "control 'tmp-2.1' do\n impact 0.3\n title '/tmp directory is owned by the root user'\n desc 'The /tmp directory must be owned by the root user'\n tag 'production','development'\n tag identifier: 'value'\n tag remediation: 'https://github.com/chef-cookbooks/audit'\n ref 'Compliance Whitepaper', url: 'https://pages.chef.io/rs/255-VFB-268/images/compliance-at-velocity2015.pdf'\n describe file '/tmp' do\n it { should be_owned_by 'root' }\n end\nend\n",
317
+ "source_location": { "ref": "/Users/vjeffrey/code/delivery/insights/data_generator/chef-client/cache/cookbooks/test-cookbook/recipes/../files/default/compliance_profiles/tmp_compliance_profile/controls/tmp.rb", "line": 12 },
318
+ "results": [
319
+ { "status": "passed", "code_desc": 'File /tmp should be owned by "root"', "run_time": 1.228845, "start_time": "2016-10-19 11:09:43 -0400" },
320
+ { "status": "passed", "code_desc": 'File /etc should be owned by "root"', "run_time": 1.238845, "start_time": "2016-10-19 11:09:43 -0400" },
321
+ ],
322
+ },
323
+ ],
324
+ "groups": [{ "title": "/tmp Compliance Profile", "controls": ["tmp-1.0", "tmp-1.1"], "id": "controls/tmp.rb" }],
325
+ "attributes": [{ "name": "syslog_pkg", "options": { "default": "rsyslog", "description": "syslog package..." } }] }],
326
+ "other_checks": [],
327
+ "statistics": { "duration": 0.032332 },
328
+ }
329
+ end
330
+
331
+ it "truncates controls results 1" do
332
+ truncated_report = reporter.truncate_controls_results(report, 5)
333
+ expect(truncated_report[:profiles][0][:controls][0][:results].length).to eq(5)
334
+ statuses = truncated_report[:profiles][0][:controls][0][:results].map { |r| r[:status] }
335
+ expect(statuses).to eq(%w{failed failed failed skipped skipped})
336
+ expect(truncated_report[:profiles][0][:controls][0][:removed_results_counts]).to eq(failed: 0, skipped: 1, passed: 3)
337
+ end
338
+
339
+ it "truncates controls results 2" do
340
+ truncated_report = reporter.truncate_controls_results(report, 5)
341
+ expect(truncated_report[:profiles][0][:controls][1][:results].length).to eq(2)
342
+ statuses = truncated_report[:profiles][0][:controls][1][:results].map { |r| r[:status] }
343
+ expect(statuses).to eq(%w{passed passed})
344
+ expect(truncated_report[:profiles][0][:controls][1][:removed_results_counts]).to eq(nil)
345
+ end
346
+
347
+ it "truncates controls results 3" do
348
+ truncated_report = reporter.truncate_controls_results(report, 0)
349
+ expect(truncated_report[:profiles][0][:controls][0][:results].length).to eq(9)
350
+ end
351
+
352
+ it "truncates controls results 4" do
353
+ truncated_report = reporter.truncate_controls_results(report, 1)
354
+ expect(truncated_report[:profiles][0][:controls][0][:results].length).to eq(1)
355
+ end
356
+ end
357
+
358
+ describe "#strip_profiles_meta" do
359
+ it "removes the metadata from seen profiles" do
360
+ expected = {
361
+ other_checks: [],
362
+ profiles: [
363
+ {
364
+ attributes: [
365
+ {
366
+ name: "syslog_pkg",
367
+ options: {
368
+ default: "rsyslog",
369
+ description: "syslog package...",
370
+ },
371
+ },
372
+ ],
373
+ controls: [
374
+ {
375
+ id: "tmp-1.0",
376
+ results: [
377
+ {
378
+ code_desc: "File /tmp should be directory",
379
+ status: "passed",
380
+ },
381
+ ],
382
+ },
383
+ {
384
+ id: "tmp-1.1",
385
+ results: [
386
+ {
387
+ code_desc: 'File /tmp should be owned by "root"',
388
+ run_time: 1.228845,
389
+ start_time: "2016-10-19 11:09:43 -0400",
390
+ status: "passed",
391
+ },
392
+ {
393
+ code_desc: 'File /tmp should be owned by "root"',
394
+ run_time: 1.228845,
395
+ start_time: "2016-10-19 11:09:43 -0400",
396
+ status: "skipped",
397
+ },
398
+ {
399
+ code_desc: "File /etc/hosts is expected to be directory",
400
+ message: "expected `File /etc/hosts.directory?` to return true, got false",
401
+ run_time: 1.228845,
402
+ start_time: "2016-10-19 11:09:43 -0400",
403
+ status: "failed",
404
+ },
405
+ ],
406
+ },
407
+ ],
408
+ sha256: "7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd",
409
+ title: "/tmp Compliance Profile",
410
+ version: "0.1.1",
411
+ },
412
+ ],
413
+ run_time_limit: 1.1,
414
+ statistics: {
415
+ duration: 0.032332,
416
+ },
417
+ version: "1.2.1",
418
+ }
419
+ expect(reporter.strip_profiles_meta(inspec_report, [], 1.1)).to eq(expected)
420
+ end
421
+
422
+ it "does not remove the metadata from missing profiles" do
423
+ expected = inspec_report.merge(run_time_limit: 1.1)
424
+ expect(reporter.strip_profiles_meta(inspec_report, ["7bd598e369970002fc6f2d16d5b988027d58b044ac3fa30ae5fc1b8492e215cd"], 1.1)).to eq(expected)
425
+ end
426
+ end
427
+ end