summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2021-09-17 18:29:09 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2021-09-17 18:31:33 -0700
commitbc5469bfe0e84b3c8195f6265686738ee5059d71 (patch)
tree19fdae32342c2db2ce906d3a1d12cf3f8ff488ee /spec
parent021107a8bfe057b8c4cff9c25c02187f55dbe60b (diff)
downloadchef-bc5469bfe0e84b3c8195f6265686738ee5059d71.tar.gz
Native compliance phase
compliance/{profiles,waivers,inputs} directories supported in cookbooks. include_profile, include_waiver, include_input as DSL methods to dynamically add compliance phase items to the run. inspec_waiver, inspect_input resources for additional sugar and a resource-like API. Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
Diffstat (limited to 'spec')
-rw-r--r--spec/integration/compliance/compliance_spec.rb60
-rw-r--r--spec/unit/compliance/input_spec.rb104
-rw-r--r--spec/unit/compliance/profile_spec.rb120
-rw-r--r--spec/unit/compliance/waiver_spec.rb104
-rw-r--r--spec/unit/resource/inspec_input_spec.rb300
-rw-r--r--spec/unit/resource/inspec_waiver_spec.rb312
6 files changed, 1000 insertions, 0 deletions
diff --git a/spec/integration/compliance/compliance_spec.rb b/spec/integration/compliance/compliance_spec.rb
index 553e947ee3..7524f7d56d 100644
--- a/spec/integration/compliance/compliance_spec.rb
+++ b/spec/integration/compliance/compliance_spec.rb
@@ -80,4 +80,64 @@ describe "chef-client with compliance phase" do
expect(result["status"]).to eq("passed")
end
end
+
+ when_the_repository "has a compliance segment" do
+ let(:report_file) { path_to("report_file.json") }
+
+ before do
+ directory "cookbooks/x" do
+ directory "compliance" do
+ directory "profiles/my_profile" do
+ file "inspec.yml", <<~FILE
+ ---
+ name: my-profile
+ FILE
+
+ directory "controls" do
+ file "my_control.rb", <<~FILE
+ control "my control" do
+ describe Dir.home do
+ it { should be_kind_of String }
+ end
+ end
+ FILE
+ end
+ end
+ end
+ file "attributes/default.rb", <<~FILE
+ default['audit']['reporter'] = "json-file"
+ default['audit']['json_file'] = {
+ "location" => "#{report_file}"
+ }
+ FILE
+ file "recipes/default.rb", <<~FILE
+ include_profile ".*::.*"
+ FILE
+ end
+ file "config/client.rb", <<~EOM
+ local_mode true
+ cookbook_path "#{path_to("cookbooks")}"
+ log_level :warn
+ EOM
+ end
+
+ it "should complete with success" do
+ result = shell_out!("#{chef_client} -c \"#{path_to("config/client.rb")}\" -r 'recipe[x]'", cwd: chef_dir)
+ result.error!
+
+ inspec_report = JSON.parse(File.read(report_file))
+ expect(inspec_report["profiles"].length).to eq(1)
+
+ profile = inspec_report["profiles"].first
+ expect(profile["name"]).to eq("my-profile")
+ expect(profile["controls"].length).to eq(1)
+
+ control = profile["controls"].first
+ expect(control["id"]).to eq("my control")
+ expect(control["results"].length).to eq(1)
+
+ result = control["results"].first
+ expect(result["status"]).to eq("passed")
+ end
+ end
end
diff --git a/spec/unit/compliance/input_spec.rb b/spec/unit/compliance/input_spec.rb
new file mode 100644
index 0000000000..1826a7501c
--- /dev/null
+++ b/spec/unit/compliance/input_spec.rb
@@ -0,0 +1,104 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+require "tempfile"
+
+describe Chef::Compliance::Input do
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:data) { { "ssh-01" => { "expiration_date" => Date.jd(2463810), "justification" => "waived, yo", "run" => false } } }
+ let(:path) { "/var/chef/cache/cookbooks/acme_compliance/compliance/inputs/default.yml" }
+ let(:cookbook_name) { "acme_compliance" }
+ let(:input) { Chef::Compliance::Input.new(events, data, path, cookbook_name) }
+
+ it "has a cookbook_name" do
+ expect(input.cookbook_name).to eql(cookbook_name)
+ end
+
+ it "has a path" do
+ expect(input.path).to eql(path)
+ end
+
+ it "has a pathname based on the path" do
+ expect(input.pathname).to eql("default")
+ end
+
+ it "is disabled" do
+ expect(input.enabled).to eql(false)
+ expect(input.enabled?).to eql(false)
+ end
+
+ it "has an event handler" do
+ expect(input.events).to eql(events)
+ end
+
+ it "can be enabled by enable!" do
+ input.enable!
+ expect(input.enabled).to eql(true)
+ expect(input.enabled?).to eql(true)
+ end
+
+ it "enabling sends an event" do
+ expect(events).to receive(:compliance_input_enabled).with(input)
+ input.enable!
+ end
+
+ it "can be disabled by disable!" do
+ input.enable!
+ input.disable!
+ expect(input.enabled).to eql(false)
+ expect(input.enabled?).to eql(false)
+ end
+
+ it "has a #for_inspec method that renders the path" do
+ expect(input.for_inspec).to eql(path)
+ end
+
+ it "doesn't render the events in the inspect output" do
+ expect(input.inspect).not_to include("events")
+ end
+
+ it "inflates objects from YAML" do
+ string = <<~EOH
+ssh-01:
+ expiration_date: 2033-07-31
+ run: false
+ justification: "waived, yo"
+ EOH
+ newinput = Chef::Compliance::Input.from_yaml(events, string, path, cookbook_name)
+ expect(newinput.data).to eql(data)
+ end
+
+ it "inflates objects from files" do
+ string = <<~EOH
+ssh-01:
+ expiration_date: 2033-07-31
+ run: false
+ justification: "waived, yo"
+ EOH
+ tempfile = Tempfile.new("chef-compliance-test")
+ tempfile.write string
+ tempfile.close
+ newinput = Chef::Compliance::Input.from_file(events, tempfile.path, cookbook_name)
+ expect(newinput.data).to eql(data)
+ end
+
+ it "inflates objects from hashes" do
+ newinput = Chef::Compliance::Input.from_hash(events, data, path, cookbook_name)
+ expect(newinput.data).to eql(data)
+ end
+end
diff --git a/spec/unit/compliance/profile_spec.rb b/spec/unit/compliance/profile_spec.rb
new file mode 100644
index 0000000000..53b7168279
--- /dev/null
+++ b/spec/unit/compliance/profile_spec.rb
@@ -0,0 +1,120 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+require "tempfile"
+
+describe Chef::Compliance::Profile do
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:data) { { "copyright" => "DevSec Hardening Framework Team", "copyright_email" => "hello@dev-sec.io", "license" => "Apache-2.0", "maintainer" => "DevSec Hardening Framework Team", "name" => "ssh-baseline", "summary" => "Test-suite for best-practice SSH hardening", "supports" => [{ "os-family" => "unix" }], "title" => "DevSec SSH Baseline", "version" => "2.6.4" } }
+ let(:path) { "/var/chef/cache/cookbooks/acme_compliance/compliance/profiles/thisdirectoryisnotthename/inspec.yml" }
+ let(:cookbook_name) { "acme_compliance" }
+ let(:profile) { Chef::Compliance::Profile.new(events, data, path, cookbook_name) }
+
+ it "has a cookbook_name" do
+ expect(profile.cookbook_name).to eql(cookbook_name)
+ end
+
+ it "has a path" do
+ expect(profile.path).to eql(path)
+ end
+
+ it "has a name based on the yml" do
+ expect(profile.name).to eql("ssh-baseline")
+ end
+
+ it "has a pathname based on the path" do
+ expect(profile.pathname).to eql("thisdirectoryisnotthename")
+ end
+
+ it "is disabled" do
+ expect(profile.enabled).to eql(false)
+ expect(profile.enabled?).to eql(false)
+ end
+
+ it "has an event handler" do
+ expect(profile.events).to eql(events)
+ end
+
+ it "can be enabled by enable!" do
+ profile.enable!
+ expect(profile.enabled).to eql(true)
+ expect(profile.enabled?).to eql(true)
+ end
+
+ it "enabling sends an event" do
+ expect(events).to receive(:compliance_profile_enabled).with(profile)
+ profile.enable!
+ end
+
+ it "can be disabled by disable!" do
+ profile.enable!
+ profile.disable!
+ expect(profile.enabled).to eql(false)
+ expect(profile.enabled?).to eql(false)
+ end
+
+ it "has a #for_inspec method that renders the path" do
+ expect(profile.for_inspec).to eql( { name: "ssh-baseline", path: "/var/chef/cache/cookbooks/acme_compliance/compliance/profiles/thisdirectoryisnotthename" } )
+ end
+
+ it "doesn't render the events in the inspect output" do
+ expect(profile.inspect).not_to include("events")
+ end
+
+ it "inflates objects from YAML" do
+ string = <<~EOH
+name: ssh-baseline#{" "}
+title: DevSec SSH Baseline#{" "}
+maintainer: DevSec Hardening Framework Team#{" "}
+copyright: DevSec Hardening Framework Team#{" "}
+copyright_email: hello@dev-sec.io#{" "}
+license: Apache-2.0#{" "}
+summary: Test-suite for best-practice SSH hardening#{" "}
+version: 2.6.4#{" "}
+supports:#{" "}
+ - os-family: unix
+ EOH
+ newprofile = Chef::Compliance::Profile.from_yaml(events, string, path, cookbook_name)
+ expect(newprofile.data).to eql(data)
+ end
+
+ it "inflates objects from files" do
+ string = <<~EOH
+name: ssh-baseline#{" "}
+title: DevSec SSH Baseline#{" "}
+maintainer: DevSec Hardening Framework Team#{" "}
+copyright: DevSec Hardening Framework Team#{" "}
+copyright_email: hello@dev-sec.io#{" "}
+license: Apache-2.0#{" "}
+summary: Test-suite for best-practice SSH hardening#{" "}
+version: 2.6.4#{" "}
+supports:#{" "}
+ - os-family: unix
+ EOH
+ tempfile = Tempfile.new("chef-compliance-test")
+ tempfile.write string
+ tempfile.close
+ newprofile = Chef::Compliance::Profile.from_file(events, tempfile.path, cookbook_name)
+ expect(newprofile.data).to eql(data)
+ end
+
+ it "inflates objects from hashes" do
+ newprofile = Chef::Compliance::Profile.from_hash(events, data, path, cookbook_name)
+ expect(newprofile.data).to eql(data)
+ end
+end
diff --git a/spec/unit/compliance/waiver_spec.rb b/spec/unit/compliance/waiver_spec.rb
new file mode 100644
index 0000000000..b079f7e231
--- /dev/null
+++ b/spec/unit/compliance/waiver_spec.rb
@@ -0,0 +1,104 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+require "tempfile"
+
+describe Chef::Compliance::Waiver do
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:data) { { "ssh-01" => { "expiration_date" => Date.jd(2463810), "justification" => "waived, yo", "run" => false } } }
+ let(:path) { "/var/chef/cache/cookbooks/acme_compliance/compliance/waivers/default.yml" }
+ let(:cookbook_name) { "acme_compliance" }
+ let(:waiver) { Chef::Compliance::Waiver.new(events, data, path, cookbook_name) }
+
+ it "has a cookbook_name" do
+ expect(waiver.cookbook_name).to eql(cookbook_name)
+ end
+
+ it "has a path" do
+ expect(waiver.path).to eql(path)
+ end
+
+ it "has a pathname based on the path" do
+ expect(waiver.pathname).to eql("default")
+ end
+
+ it "is disabled" do
+ expect(waiver.enabled).to eql(false)
+ expect(waiver.enabled?).to eql(false)
+ end
+
+ it "has an event handler" do
+ expect(waiver.events).to eql(events)
+ end
+
+ it "can be enabled by enable!" do
+ waiver.enable!
+ expect(waiver.enabled).to eql(true)
+ expect(waiver.enabled?).to eql(true)
+ end
+
+ it "enabling sends an event" do
+ expect(events).to receive(:compliance_waiver_enabled).with(waiver)
+ waiver.enable!
+ end
+
+ it "can be disabled by disable!" do
+ waiver.enable!
+ waiver.disable!
+ expect(waiver.enabled).to eql(false)
+ expect(waiver.enabled?).to eql(false)
+ end
+
+ it "has a #for_inspec method that renders the path" do
+ expect(waiver.for_inspec).to eql(path)
+ end
+
+ it "doesn't render the events in the inspect output" do
+ expect(waiver.inspect).not_to include("events")
+ end
+
+ it "inflates objects from YAML" do
+ string = <<~EOH
+ssh-01:
+ expiration_date: 2033-07-31
+ run: false
+ justification: "waived, yo"
+ EOH
+ newwaiver = Chef::Compliance::Waiver.from_yaml(events, string, path, cookbook_name)
+ expect(newwaiver.data).to eql(data)
+ end
+
+ it "inflates objects from files" do
+ string = <<~EOH
+ssh-01:
+ expiration_date: 2033-07-31
+ run: false
+ justification: "waived, yo"
+ EOH
+ tempfile = Tempfile.new("chef-compliance-test")
+ tempfile.write string
+ tempfile.close
+ newwaiver = Chef::Compliance::Waiver.from_file(events, tempfile.path, cookbook_name)
+ expect(newwaiver.data).to eql(data)
+ end
+
+ it "inflates objects from hashes" do
+ newwaiver = Chef::Compliance::Waiver.from_hash(events, data, path, cookbook_name)
+ expect(newwaiver.data).to eql(data)
+ end
+end
diff --git a/spec/unit/resource/inspec_input_spec.rb b/spec/unit/resource/inspec_input_spec.rb
new file mode 100644
index 0000000000..4acdfd60a9
--- /dev/null
+++ b/spec/unit/resource/inspec_input_spec.rb
@@ -0,0 +1,300 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::InspecInput do
+ def load_input(filename)
+ path = "/var/chef/cache/cookbooks/acme_compliance/compliance/inputs/#{filename}"
+ run_context.input_collection << Chef::Compliance::Input.from_yaml(events, input_yaml, path, "acme_compliance")
+ end
+
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) do
+ Chef::RunContext.new(node, {}, events).tap do |rc|
+ end
+ end
+ let(:collection) { double("resource collection") }
+ let(:input_yaml) do
+ <<~EOH
+ssh_custom_path: "/whatever2"
+ EOH
+ end
+ let(:input_json) do
+ <<~EOH
+ { "ssh_custom_path": "/whatever2" }
+ EOH
+ end
+ let(:input_toml) do
+ <<~EOH
+ssh_custom_path = "/whatever2"
+ EOH
+ end
+ let(:input_hash) do
+ { ssh_custom_path: "/whatever2" }
+ end
+ let(:resource) do
+ Chef::Resource::InspecInput.new("ssh-01", run_context)
+ end
+ let(:provider) { resource.provider_for_action(:add) }
+
+ before do
+ allow(run_context).to receive(:resource_collection).and_return(collection)
+ end
+
+ it "sets the default action as :add" do
+ expect(resource.action).to eql([:add])
+ end
+
+ context "with a input in a cookbook" do
+ it "enables the input by the name of the cookbook" do
+ load_input("default.yml")
+ resource.name "acme_compliance"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "enables the input with a regular expression for the cookbook" do
+ load_input("default.yml")
+ resource.name "acme_comp.*"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "enables the input with an explicit name" do
+ load_input("default.yml")
+ resource.name "acme_compliance::default"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails when the cookbook name is wrong" do
+ load_input("default.yml")
+ resource.name "evil_compliance"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "enables the input when its not named default" do
+ load_input("ssh01.yml")
+ resource.name "acme_compliance::ssh01"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails when it is not named default and you attempt to enable the default" do
+ load_input("ssh01.yml")
+ resource.name "acme_compliance"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "succeeds with a regexp that matches the cookbook name" do
+ load_input("ssh01.yml")
+ resource.name "acme_comp.*::ssh01"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "succeeds with a regexp that matches the file name" do
+ load_input("ssh01.yml")
+ resource.name "acme_compliance::ssh.*"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "succeeds with a regexps for both the file name and cookbook name" do
+ load_input("ssh01.yml")
+ resource.name "acme_comp.*::ssh.*"
+ resource.run_action(:add)
+ expect(run_context.input_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails with regexps that do not match" do
+ load_input("ssh01.yml")
+ resource.name "evil_comp.*::etcd.*"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "substring matches without regexps should fail when they are at the end" do
+ load_input("ssh01.yml")
+ resource.name "acme_complianc::ssh0"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "substring matches without regexps should fail when they are at the start" do
+ load_input("ssh01.yml")
+ resource.name "cme_compliance::sh01"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+ end
+
+ context "with a input in a file" do
+ it "loads a YAML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yaml"])
+ tempfile.write input_yaml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YAML file in a source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yaml"])
+ tempfile.write input_yaml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yml"])
+ tempfile.write input_yaml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YML file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yml"])
+ tempfile.write input_yaml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a JSON file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".json"])
+ tempfile.write input_json
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a JSON file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".json"])
+ tempfile.write input_json
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a TOML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".toml"])
+ tempfile.write input_toml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a TOML file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".toml"])
+ tempfile.write input_toml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a Hash" do
+ resource.source input_hash
+
+ resource.run_action(:add)
+
+ expect(run_context.input_collection.first).to be_enabled
+ expect(run_context.input_collection.size).to be 1
+ expect(run_context.input_collection.first.cookbook_name).to be nil
+ expect(run_context.input_collection.first.path).to be nil
+ expect(run_context.input_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+ end
+end
diff --git a/spec/unit/resource/inspec_waiver_spec.rb b/spec/unit/resource/inspec_waiver_spec.rb
new file mode 100644
index 0000000000..3154bcc9fa
--- /dev/null
+++ b/spec/unit/resource/inspec_waiver_spec.rb
@@ -0,0 +1,312 @@
+#
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::InspecWaiver do
+ def load_waiver(filename)
+ path = "/var/chef/cache/cookbooks/acme_compliance/compliance/waivers/#{filename}"
+ run_context.waiver_collection << Chef::Compliance::Waiver.from_yaml(events, waiver_yaml, path, "acme_compliance")
+ end
+
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) do
+ Chef::RunContext.new(node, {}, events).tap do |rc|
+ end
+ end
+ let(:collection) { double("resource collection") }
+ let(:waiver_yaml) do
+ <<~EOH
+ssh-01:
+ expiration_date: 2033-07-31
+ run: false
+ justification: "waived, yo"
+ EOH
+ end
+ let(:waiver_json) do
+ <<~EOH
+{ "ssh-01": {
+ "expiration_date": "2033-07-31",
+ "run": false,
+ "justification": "waived, yo"
+ } }
+ EOH
+ end
+ let(:waiver_toml) do
+ <<~EOH
+[ssh-01]
+expiration_date = 2033-07-31T00:00:00.000Z
+run = false
+justification = "waived, yo"
+ EOH
+ end
+ let(:waiver_hash) do
+ { "ssh-01" => {
+ "expiration_date" => "2033-07-31",
+ "run" => false,
+ "justification" => "waived, yo",
+ } }
+ end
+ let(:resource) do
+ Chef::Resource::InspecWaiver.new("ssh-01", run_context)
+ end
+ let(:provider) { resource.provider_for_action(:add) }
+
+ before do
+ allow(run_context).to receive(:resource_collection).and_return(collection)
+ end
+
+ it "sets the default action as :add" do
+ expect(resource.action).to eql([:add])
+ end
+
+ context "with a waiver in a cookbook" do
+ it "enables the waiver by the name of the cookbook" do
+ load_waiver("default.yml")
+ resource.name "acme_compliance"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "enables the waiver with a regular expression for the cookbook" do
+ load_waiver("default.yml")
+ resource.name "acme_comp.*"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "enables the waiver with an explicit name" do
+ load_waiver("default.yml")
+ resource.name "acme_compliance::default"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails when the cookbook name is wrong" do
+ load_waiver("default.yml")
+ resource.name "evil_compliance"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "enables the waiver when its not named default" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_compliance::ssh01"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails when it is not named default and you attempt to enable the default" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_compliance"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "succeeds with a regexp that matches the cookbook name" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_comp.*::ssh01"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "succeeds with a regexp that matches the file name" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_compliance::ssh.*"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "succeeds with a regexps for both the file name and cookbook name" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_comp.*::ssh.*"
+ resource.run_action(:add)
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "fails with regexps that do not match" do
+ load_waiver("ssh01.yml")
+ resource.name "evil_comp.*::etcd.*"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "substring matches without regexps should fail when they are at the end" do
+ load_waiver("ssh01.yml")
+ resource.name "acme_complianc::ssh0"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+
+ it "substring matches without regexps should fail when they are at the start" do
+ load_waiver("ssh01.yml")
+ resource.name "cme_compliance::sh01"
+ expect { resource.run_action(:add) }.to raise_error(StandardError)
+ end
+ end
+
+ context "with a waiver in a file" do
+ it "loads a YAML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yaml"])
+ tempfile.write waiver_yaml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YAML file in a source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yaml"])
+ tempfile.write waiver_yaml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yml"])
+ tempfile.write waiver_yaml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a YML file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".yml"])
+ tempfile.write waiver_yaml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a JSON file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".json"])
+ tempfile.write waiver_json
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a JSON file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".json"])
+ tempfile.write waiver_json
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a TOML file" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".toml"])
+ tempfile.write waiver_toml
+ tempfile.close
+ resource.name tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a TOML file using the source attribute" do
+ tempfile = Tempfile.new(["spec-compliance-test", ".toml"])
+ tempfile.write waiver_toml
+ tempfile.close
+ resource.name "my-resource-name"
+ resource.source tempfile.path
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "loads a Hash" do
+ resource.source waiver_hash
+
+ resource.run_action(:add)
+
+ expect(run_context.waiver_collection.first).to be_enabled
+ expect(run_context.waiver_collection.size).to be 1
+ expect(run_context.waiver_collection.first.cookbook_name).to be nil
+ expect(run_context.waiver_collection.first.path).to be nil
+ expect(run_context.waiver_collection.first.pathname).to be nil
+ expect(resource).not_to be_updated_by_last_action
+ end
+ end
+end