diff options
author | Tim Smith <tsmith@chef.io> | 2018-09-08 17:46:00 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-08 17:46:00 -0700 |
commit | 7a5b2fce1e829054d9395bbc316f98b102d2e9ac (patch) | |
tree | ab423612a50a7b073bfec9d0d56e152cf8495835 | |
parent | 68db80ab4b8f21fca6d3a2121ced524ac9f09167 (diff) | |
parent | 489c34baa27011c43a6c70597967f30999ab66f3 (diff) | |
download | ohai-7a5b2fce1e829054d9395bbc316f98b102d2e9ac.tar.gz |
Merge pull request #1238 from MsysTechnologiesllc/nimesh/MSYS-894_add_encryption_status
Add "EncryptionStatus" to each volume on Windows
-rw-r--r-- | lib/ohai/plugins/windows/filesystem.rb | 137 | ||||
-rw-r--r-- | spec/unit/plugins/windows/filesystem_spec.rb | 226 |
2 files changed, 341 insertions, 22 deletions
diff --git a/lib/ohai/plugins/windows/filesystem.rb b/lib/ohai/plugins/windows/filesystem.rb index 8cab4955..ef9bce73 100644 --- a/lib/ohai/plugins/windows/filesystem.rb +++ b/lib/ohai/plugins/windows/filesystem.rb @@ -19,36 +19,129 @@ Ohai.plugin(:Filesystem) do provides "filesystem" - collect_data(:windows) do + # Volume encryption or decryption status + # + # @see https://docs.microsoft.com/en-us/windows/desktop/SecProv/getconversionstatus-win32-encryptablevolume#parameters + # + CONVERSION_STATUS = %w{FullyDecrypted FullyEncrypted EncryptionInProgress + DecryptionInProgress EncryptionPaused DecryptionPaused}.freeze - require "wmi-lite/wmi" + # Returns a Mash loaded with logical details + # + # Uses Win32_LogicalDisk and logical_properties to return encryption details of volumes. + # + # Returns an empty Mash in case of any WMI exception. + # + # @see https://docs.microsoft.com/en-us/windows/desktop/CIMWin32Prov/win32-logicaldisk + # + # @return [Mash] + # + def logical_info + wmi = WmiLite::Wmi.new("Root\\CIMV2") - fs = Mash.new - ld_info = Mash.new + # Note: we should really be parsing Win32_Volume and Win32_Mapped drive. + disks = wmi.instances_of("Win32_LogicalDisk") + logical_properties(disks) + rescue WmiLite::WmiException + Ohai::Log.debug("Unable to access Win32_LogicalDisk. Skipping logical details") + Mash.new + end - wmi = WmiLite::Wmi.new + # Returns a Mash loaded with encryption details + # + # Uses Win32_EncryptableVolume and encryption_properties to return encryption details of volumes. + # + # Returns an empty Mash in case of any WMI exception. + # + # @note We are fetching Encryption Status only as of now + # + # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa376483(v=vs.85).aspx + # + # @return [Mash] + # + def encryptable_info + wmi = WmiLite::Wmi.new("Root\\CIMV2\\Security\\MicrosoftVolumeEncryption") + disks = wmi.instances_of("Win32_EncryptableVolume") + encryption_properties(disks) + rescue WmiLite::WmiException + Ohai::Log.debug("Unable to access Win32_EncryptableVolume. Skipping encryptable details") + Mash.new + end - # Grab filesystem data from WMI - # Note: we should really be parsing Win32_Volume and Win32_Mapped drive - disks = wmi.instances_of("Win32_LogicalDisk") + # Refines and calculates logical properties out of given instances + # + # @param [WmiLite::Wmi::Instance] disks + # + # @return [Mash] Each drive containing following properties: + # + # * :kb_size (Integer) + # * :kb_available (Integer) + # * :kb_used (Integer) + # * :percent_used (Integer) + # * :mount (String) + # * :fs_type (String) + # * :volume_name (String) + # + def logical_properties(disks) + properties = Mash.new + disks.each do |disk| + property = Mash.new + drive = disk["deviceid"] + property[:kb_size] = disk["size"].to_i / 1000 + property[:kb_available] = disk["freespace"].to_i / 1000 + property[:kb_used] = property[:kb_size] - property[:kb_available] + property[:percent_used] = (property[:kb_size] == 0 ? 0 : (property[:kb_used] * 100 / property[:kb_size])) + property[:mount] = disk["name"] + property[:fs_type] = disk["filesystem"].to_s.downcase + property[:volume_name] = disk["volumename"].to_s.downcase + properties[drive] = property + end + properties + end + # Refines and calculates encryption properties out of given instances + # + # @param [WmiLite::Wmi::Instance] disks + # + # @return [Mash] Each drive containing following properties: + # + # * :encryption_status (String) + # + def encryption_properties(disks) + properties = Mash.new disks.each do |disk| - filesystem = disk["deviceid"] - fs[filesystem] = Mash.new - ld_info[filesystem] = Mash.new - disk.wmi_ole_object.properties_.each do |p| - ld_info[filesystem][p.name.wmi_underscore.to_sym] = disk[p.name.downcase] + drive = disk["driveletter"] + property = Mash.new + property[:encryption_status] = disk["conversionstatus"] ? CONVERSION_STATUS[disk["conversionstatus"]] : "" + properties[drive] = property + end + properties + end + + # Merges all the various properties of filesystems + # + # @param [Array<Mash>] disks_info + # Array of the Mashes containing disk properties + # + # @return [Mash] + # + def merge_info(disks_info) + fs = Mash.new + disks_info.each do |info| + info.each do |disk, data| + if fs[disk] + fs[disk].merge!(data) + else + fs[disk] = data.dup + end end - fs[filesystem][:kb_size] = ld_info[filesystem][:size].to_i / 1000 - fs[filesystem][:kb_available] = ld_info[filesystem][:free_space].to_i / 1000 - fs[filesystem][:kb_used] = fs[filesystem][:kb_size].to_i - fs[filesystem][:kb_available].to_i - fs[filesystem][:percent_used] = (fs[filesystem][:kb_size].to_i != 0 ? fs[filesystem][:kb_used].to_i * 100 / fs[filesystem][:kb_size].to_i : 0) - fs[filesystem][:mount] = ld_info[filesystem][:name] - fs[filesystem][:fs_type] = ld_info[filesystem][:file_system].downcase unless ld_info[filesystem][:file_system].nil? - fs[filesystem][:volume_name] = ld_info[filesystem][:volume_name] end + fs + end - # Set the filesystem data - filesystem fs + collect_data(:windows) do + require "wmi-lite/wmi" + filesystem merge_info([logical_info, + encryptable_info]) end end diff --git a/spec/unit/plugins/windows/filesystem_spec.rb b/spec/unit/plugins/windows/filesystem_spec.rb new file mode 100644 index 00000000..45b8be81 --- /dev/null +++ b/spec/unit/plugins/windows/filesystem_spec.rb @@ -0,0 +1,226 @@ +# +# Author:: Nimesh Pathi <nimesh.patni@msystechnologies.com> +# Copyright:: Copyright (c) 2018 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_relative "../../../spec_helper.rb" +require "wmi-lite/wmi" + +describe Ohai::System, "Windows Filesystem Plugin", :windows_only do + let(:plugin) { get_plugin("windows/filesystem") } + + let(:success) { true } + + let(:logical_disks_instances) do + [ + { + "caption" => "C:", + "deviceid" => "C:", + "size" => "10000000", + "filesystem" => "NTFS", + "freespace" => "100000", + "name" => "C:", + "volumename " => "", + }, + { + "caption" => "D:", + "deviceid" => "D:", + "size" => "10000000", + "filesystem" => "FAT32", + "freespace" => "100000", + "name" => "D:" + # Lets not pass "volumename" for this drive + } + ] + end + + let(:encryptable_volume_instances) do + [ + { + "conversionstatus" => 0, + "driveletter" => "C:", + }, + { + "conversionstatus" => 2, + "driveletter" => "D:", + } + ] + end + + let(:wmi_exception) do + namespace = "Exception while testing" + exception = WIN32OLERuntimeError.new(namespace) + WmiLite::WmiException.new(exception, :ConnectServer, @namespace) + end + + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:windows) + end + + describe "#logical_properties" do + let(:disks) { logical_disks_instances } + let(:logical_props) { %i{kb_size kb_available kb_used percent_used mount fs_type volume_name} } + + it "Returns a mash" do + expect(plugin.logical_properties(disks)).to be_a(Mash) + end + + it "Returns an empty mash when blank array is passed" do + expect(plugin.logical_properties([])).to be_a(Mash) + expect(plugin.logical_properties([])).to be_empty + end + + it "Returns properties without values when there is no disk information" do + data = plugin.logical_properties([{}]) + expect(data[nil].symbolize_keys.keys).to eq(logical_props) + expect(data[nil]["kb_used"]).to eq(0) + expect(data[nil]["fs_type"]).to be_empty + end + + it "Refines required logical properties out of given instance" do + data = plugin.logical_properties(disks) + expect(data["C:"].symbolize_keys.keys).to eq(logical_props) + expect(data["D:"].symbolize_keys.keys).to eq(logical_props) + end + + it "Calculates logical properties out of given instance" do + data = plugin.logical_properties(disks) + expect(data["C:"]["kb_used"]).to eq(data["D:"]["kb_used"]).and eq(9900) + expect(data["C:"]["percent_used"]).to eq(data["D:"]["percent_used"]).and eq(99) + expect(data["C:"]["fs_type"]).to eq("ntfs") + expect(data["D:"]["fs_type"]).to eq("fat32") + end + end + + describe "#logical_info" do + it "Returns an empty mash when wmi namespace does not exists" do + allow(WmiLite::Wmi).to receive(:new).and_raise(wmi_exception) + expect(plugin.logical_info).to be_a(Mash) + expect(plugin.logical_info).to be_empty + end + + it "Returns an empty mash when Win32_LogicalDisk could not be processed" do + allow(WmiLite::Wmi).to receive(:new).and_return(success) + allow(success) + .to receive(:instances_of) + .with("Win32_LogicalDisk") + .and_raise(wmi_exception) + expect(plugin.logical_info).to be_a(Mash) + expect(plugin.logical_info).to be_empty + end + + it "Returns a Mash loaded with logical details" do + allow(WmiLite::Wmi).to receive(:new).and_return(success) + allow(success) + .to receive(:instances_of) + .with("Win32_LogicalDisk") + .and_return(logical_disks_instances) + expect(plugin.logical_info).to be_a(Mash) + expect(plugin.logical_info).not_to be_empty + end + end + + describe "#encryption_properties" do + let(:disks) { encryptable_volume_instances } + let(:encryption_props) { [:encryption_status] } + + it "Returns a mash" do + expect(plugin.encryption_properties(disks)).to be_a(Mash) + end + + it "Returns an empty mash when blank array is passed" do + expect(plugin.encryption_properties([])).to be_a(Mash) + expect(plugin.encryption_properties([])).to be_empty + end + + it "Returns properties without values when there is no disk information" do + data = plugin.encryption_properties([{}]) + expect(data[nil].symbolize_keys.keys).to eq(encryption_props) + expect(data[nil]["encryption_status"]).to be_empty + end + + it "Refines required encryption properties out of given instance" do + data = plugin.encryption_properties(disks) + expect(data["C:"].symbolize_keys.keys).to eq(encryption_props) + expect(data["D:"].symbolize_keys.keys).to eq(encryption_props) + end + + it "Calculates encryption properties out of given instance" do + data = plugin.encryption_properties(disks) + expect(data["C:"]["encryption_status"]).to eq("FullyDecrypted") + expect(data["D:"]["encryption_status"]).to eq("EncryptionInProgress") + end + end + + describe "#encryptable_info" do + it "Returns an empty mash when wmi namespace does not exists" do + allow(WmiLite::Wmi).to receive(:new).and_raise(wmi_exception) + expect(plugin.encryptable_info).to be_a(Mash) + expect(plugin.encryptable_info).to be_empty + end + + it "Returns an empty mash when Win32_EncryptableVolume could not be processed" do + allow(WmiLite::Wmi).to receive(:new).and_return(success) + allow(success) + .to receive(:instances_of) + .with("Win32_EncryptableVolume") + .and_raise(wmi_exception) + expect(plugin.encryptable_info).to be_a(Mash) + expect(plugin.encryptable_info).to be_empty + end + + it "Returns a Mash loaded with encryption details" do + allow(WmiLite::Wmi).to receive(:new).and_return(success) + allow(success) + .to receive(:instances_of) + .with("Win32_EncryptableVolume") + .and_return(encryptable_volume_instances) + expect(plugin.encryptable_info).to be_a(Mash) + expect(plugin.encryptable_info).not_to be_empty + end + end + + describe "#merge_info" do + let(:info1) do + { "drive1" => { "x" => 10, "y" => "test1" }, + "drive2" => { "x" => 20, "z" => "test2" } } + end + let(:info2) do + { "drive1" => { "k" => 10, "l" => "test1" }, + "drive2" => { "l" => 20, "m" => "test2" } } + end + let(:info3) { { "drive1" => { "o" => 10, "p" => "test1" } } } + let(:info4) { { "drive2" => { "q" => 10, "r" => "test1" } } } + + it "Returns an empty mash when no info is passed" do + expect(plugin.merge_info([])).to be_a(Mash) + expect(plugin.merge_info([])).to be_empty + end + + it "Merges all the various properties of filesystems" do + expect(plugin.merge_info([info1, info2, info3, info4])) + .to eq("drive1" => { "x" => 10, "y" => "test1", "k" => 10, "l" => "test1", "o" => 10, "p" => "test1" }, + "drive2" => { "x" => 20, "z" => "test2", "l" => 20, "m" => "test2", "q" => 10, "r" => "test1" }) + end + + it "Does not affect any core information after processing" do + expect(plugin.merge_info([info3, info4])).to eq("drive1" => { "o" => 10, "p" => "test1" }, + "drive2" => { "q" => 10, "r" => "test1" }) + expect(info3).to eq("drive1" => { "o" => 10, "p" => "test1" }) + expect(info4).to eq("drive2" => { "q" => 10, "r" => "test1" }) + end + end +end |