diff options
author | Tim Smith <tsmith@chef.io> | 2021-09-23 16:30:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-23 16:30:29 -0700 |
commit | f7f38591aa98dac71e2b4c83c23cfe39b16b9d99 (patch) | |
tree | 2f2baf87cd510ea997a7b23a5f16906440ffbafb /spec | |
parent | a9104bb5a2ec6efa0b1208fa39a3f04ad7f7b5c1 (diff) | |
parent | 1cee6c11a99b05b9fd5eef0665325f6fa1bbaa96 (diff) | |
download | chef-f7f38591aa98dac71e2b4c83c23cfe39b16b9d99.tar.gz |
Merge pull request #11904 from chef/lcg/compliance-segment
Native compliance phase
Diffstat (limited to 'spec')
-rw-r--r-- | spec/integration/compliance/compliance_spec.rb | 60 | ||||
-rw-r--r-- | spec/unit/compliance/input_spec.rb | 104 | ||||
-rw-r--r-- | spec/unit/compliance/profile_spec.rb | 120 | ||||
-rw-r--r-- | spec/unit/compliance/waiver_spec.rb | 104 | ||||
-rw-r--r-- | spec/unit/resource/inspec_input_spec.rb | 300 | ||||
-rw-r--r-- | spec/unit/resource/inspec_waiver_spec.rb | 312 |
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..a65adfb284 --- /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 #inspec_data method that renders the data" do + expect(input.inspec_data).to eql(data) + 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..8487b26691 --- /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 #inspec_data method that renders the path" do + expect(profile.inspec_data).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..e001e3a97d --- /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 #inspec_data method that renders the data" do + expect(waiver.inspec_data).to eql(data) + 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 |