summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-09-08 17:46:00 -0700
committerGitHub <noreply@github.com>2018-09-08 17:46:00 -0700
commit7a5b2fce1e829054d9395bbc316f98b102d2e9ac (patch)
treeab423612a50a7b073bfec9d0d56e152cf8495835
parent68db80ab4b8f21fca6d3a2121ced524ac9f09167 (diff)
parent489c34baa27011c43a6c70597967f30999ab66f3 (diff)
downloadohai-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.rb137
-rw-r--r--spec/unit/plugins/windows/filesystem_spec.rb226
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