itchef/cookbooks/cpe_applocker/spec/default_spec.rb (305 lines of code) (raw):

# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 # # Cookbook Name:: cpe_applocker # Recipe:: default # # Copyright (c) Facebook, Inc. and its affiliates. # # 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 'chefspec' require 'nokogiri' require_relative '../libraries/applocker_helpers' def mock_cpe_applocker_enable(enabled) is_enabled = { 'enabled' => enabled, } allow(applocker).to receive(:cpe_applocker_enabled?).and_return(is_enabled) end def mock_get_applocker_rules(enabled) mock_rules = { 'Appx' => { 'mode' => enabled ? 'AuditOnly' : 'NotConfigured', 'rules' => [], }, 'Dll' => { 'mode' => enabled ? 'Enforce' : 'NotConfigured', 'rules' => [], }, 'Exe' => { 'mode' => enabled ? 'Enforce' : 'NotConfigured', 'rules' => enabled ? [ # Rule to allow all applications for Administrators group { 'type' => 'path', 'name' => 'test_filepath', 'id' => 'fd686d83-a829', 'description' => 'Test file path rule set', 'action' => 'Allow', 'user_or_group_sid' => 'S-1-5-32-544', 'conditions' => [ { 'path' => '%OSDRIVE%\\tools\\*', }, ], }, # Rule to ban calc.exe for everyone { 'type' => 'hash', 'name' => 'test_filehash', 'id' => 'f8b0f25b-776f', 'description' => 'Test file hash rule set', 'action' => 'Deny', 'user_or_group_sid' => 'S-1-1-0', 'conditions' => [ { 'type' => 'SHA256', 'data' => '0x267FEDEDD79AAAF7CFA62ECAA29BC0D' + '8F5553FBD007A48BEC76C177BB7242CBC', 'file_name' => 'calc.exe', 'file_length' => '27648', }, ], }, # Rule to block Teamviewer publisher cert { 'type' => 'certificate', 'name' => 'test_filepublisher', 'id' => 'acd2e5a8-6b8f', 'description' => 'Test file publisher rule set', 'action' => 'Deny', 'user_or_group_sid' => 'S-1-1-0', 'conditions' => [ { 'publisher' => 'O=TEAMVIEWER GMBH, L=GOPPINGEN, ' + 'S=BADEN-WURTTEMBERG, C=DE', 'product_name' => '*', 'binary_name' => '*', 'binary_version' => { 'low' => '*', 'high' => '*' }, }, ], }, ] : [], }, 'Script' => { 'mode' => enabled ? 'AuditOnly' : 'NotConfigured', 'rules' => [], }, 'Msi' => { 'mode' => enabled ? 'AuditOnly' : 'NotConfigured', 'rules' => [], }, } allow(applocker).to receive(:get_applocker_rules).and_return(mock_rules) end describe CPE::Applocker do let(:applocker) { Class.new { extend CPE::Applocker } } applocker_configured = '<AppLockerPolicy Version="1"><RuleCollection ' + 'Type="Appx" EnforcementMode="AuditOnly"/><RuleCollection Type="Dll" ' + 'EnforcementMode="Enforce"/><RuleCollection Type="Exe" EnforcementMode=' + '"Enforce"><FilePathRule Name="test_filepath" Id="fd686d83-a829" ' + 'Description="Test file path rule set" Action="Allow" UserOrGroupSid=' + '"S-1-5-32-544"><Conditions><FilePathCondition Path="%OSDRIVE%\tools' + '\*"/></Conditions></FilePathRule><FileHashRule Name="test_filehash" ' + 'Id="f8b0f25b-776f" Description="Test file hash rule set" Action="Deny" ' + 'UserOrGroupSid="S-1-1-0"><Conditions><FileHashCondition><FileHash ' + 'Type="SHA256" Data="0x267FEDEDD79AAAF7CFA62ECAA29BC0D8F5553FBD007A4' + '8BEC76C177BB7242CBC" SourceFileName="calc.exe" SourceFileLength=' + '"27648"/></FileHashCondition></Conditions></FileHashRule><FilePublisher' + 'Rule Name="test_filepublisher" Id="acd2e5a8-6b8f" Description="Test ' + 'file publisher rule set" Action="Deny" UserOrGroupSid="S-1-1-0">' + '<Conditions><FilePublisherCondition PublisherName="O=TEAMVIEWER GMBH, ' + 'L=GOPPINGEN, S=BADEN-WURTTEMBERG, C=DE" ProductName="*" BinaryName="*">' + '<BinaryVersionRange LowSection="*" HighSection="*"/></FilePublisher' + 'Condition></Conditions></FilePublisherRule></RuleCollection><Rule' + 'Collection Type="Script" EnforcementMode="AuditOnly"/><RuleCollection ' + 'Type="Msi" EnforcementMode="AuditOnly"/></AppLockerPolicy>' applocker_disabled = '<AppLockerPolicy Version="1"><RuleCollection ' + 'Type="Appx" EnforcementMode="NotConfigured"/><RuleCollection Type=' + '"Dll" EnforcementMode="NotConfigured"/><RuleCollection Type="Exe" ' + 'EnforcementMode="NotConfigured"/><RuleCollection Type="Script" ' + 'EnforcementMode="NotConfigured"/><RuleCollection Type="Msi" ' + 'EnforcementMode="NotConfigured"/></AppLockerPolicy>' applocker_non_en_charset = <<EOF <AppLockerPolicy Version="1"> <RuleCollection Type="Exe" EnforcementMode="NotConfigured"> <FilePublisherRule Name="test_filepublisher" Id="acd2e5a8-6b8f" Description="Test file publisher rule set" Action="Deny" UserOrGroupSid="S-1-1-0"> <Conditions> <FilePublisherCondition PublisherName="O=TEAMVIEWER GMBH, L=GÖPPINGEN, S=BADEN-WÜRTTEMBERG, C=DE" ProductName="*" BinaryName="*"> <BinaryVersionRange LowSection="*" HighSection="*"/> </FilePublisherCondition> </Conditions> </FilePublisherRule> </RuleCollection> </AppLockerPolicy> EOF context 'When Applocker is enabled' do before do mock_cpe_applocker_enable(true) mock_get_applocker_rules(true) end it 'should render a valid xml string' do # Is this test valuable? Seems like we're just testing Nokogiris # ability to render XML correctly. expect(Nokogiri::XML(applocker.gen_applocker_xml).errors.length).to eql(0) end it 'should be different from NotConfigured settings' do expect(applocker.gen_applocker_xml).to eql(applocker_configured.strip) end end context 'When Applocker is disabled' do before do mock_cpe_applocker_enable(false) mock_get_applocker_rules(false) end it 'should render a valid xml string' do # Is this test valuable? Seems like we're just testing Nokogiris # ability to render XML correctly. expect(Nokogiri::XML(applocker.gen_applocker_xml).errors.length).to eql(0) end it 'should contain all NotConfigured settings' do expect(applocker.gen_applocker_xml).to eql(applocker_disabled.strip) end end # This is an explicit test to ensure that publishers with foreign # character sets are rendered and represented correctly by our helper # functions. This is important, as if we mess this up certificate deny/ # allowlisting will break, thus I feel it merits its own test. context 'When processing data with non-EN charsets' do include CPE::Applocker it 'should properly render publisher information' do state = xml_to_hash( Nokogiri::XML(applocker_non_en_charset), ) rule = state['Exe']['rules'][0] expect(rule['type']).to eql('certificate') expect(rule['conditions'][0]['publisher']).to eql( 'O=TEAMVIEWER GMBH, L=GÖPPINGEN, S=BADEN-WÜRTTEMBERG, C=DE', ) end end # Test the creation of our FilePathRules context 'When rendering FilePathRule xml' do include CPE::Applocker before do mock_cpe_applocker_enable(true) mock_get_applocker_rules(true) end it 'should return a valid applocker file path rule' do # First, create a 'stub' AppLocker policy with one FilePathRule rule = applocker.get_applocker_rules['Exe']['rules'][0] builder = Nokogiri::XML::Builder.new do |xml| xml.AppLockerPolicy(:Version => 1) do xml.RuleCollection(:Type => 'Exe', :EnforcementMode => 'NotConfigured') do gen_file_path_rule(rule, xml) end end # End of AppLockerPolicy end # End of Xml Builder # This expected policy has been tested applying on a local machine expected_policy = '<AppLockerPolicy Version="1"><RuleCollection ' + 'Type="Exe" EnforcementMode="NotConfigured"><FilePathRule ' + 'Name="test_filepath" Id="fd686d83-a829" Description="Test ' + 'file path rule set" Action="Allow" UserOrGroupSid="S-1-5-32-' + '544"><Conditions><FilePathCondition Path="%OSDRIVE%\tools\*"' + '/></Conditions></FilePathRule></RuleCollection></AppLockerPolicy>' expect(builder.to_xml( :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION, ).strip).to eql(expected_policy) end end # Test the creation of our FileHashRules context 'When rendering FileHashRules xml' do include CPE::Applocker before do mock_cpe_applocker_enable(true) mock_get_applocker_rules(true) end it 'should return a valid applocker file hash rule' do # First, create a 'stub' AppLocker policy with one FileHashRule rule = applocker.get_applocker_rules['Exe']['rules'][1] builder = Nokogiri::XML::Builder.new do |xml| xml.AppLockerPolicy(:Version => 1) do xml.RuleCollection(:Type => 'Exe', :EnforcementMode => 'NotConfigured') do gen_file_hash_rule(rule, xml) end end # End of AppLockerPolicy end # End of Xml Builder expected_policy = '<AppLockerPolicy Version="1"><RuleCollection ' + 'Type="Exe" EnforcementMode="NotConfigured"><FileHashRule Name=' + '"test_filehash" Id="f8b0f25b-776f" Description="Test file hash ' + 'rule set" Action="Deny" UserOrGroupSid="S-1-1-0"><Conditions>' + '<FileHashCondition><FileHash Type="SHA256" Data="0x267FEDEDD79' + 'AAAF7CFA62ECAA29BC0D8F5553FBD007A48BEC76C177BB7242CBC" Source' + 'FileName="calc.exe" SourceFileLength="27648"/></FileHash' + 'Condition></Conditions></FileHashRule></RuleCollection>' + '</AppLockerPolicy>' expect(builder.to_xml( :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION, ).strip).to eql(expected_policy) end end # Test the creation of our FilePublisherRules context 'When rendering FilePublisherRules xml' do include CPE::Applocker before do mock_cpe_applocker_enable(true) mock_get_applocker_rules(true) end it 'should return a valid applocker file publisher rule' do # First, create a 'stub' AppLocker policy with one FilePublisherRule rule = applocker.get_applocker_rules['Exe']['rules'][2] builder = Nokogiri::XML::Builder.new do |xml| xml.AppLockerPolicy(:Version => 1) do xml.RuleCollection(:Type => 'Exe', :EnforcementMode => 'NotConfigured') do gen_file_publisher_rule(rule, xml) end end # End of AppLockerPolicy end # End of Xml Builder expected_policy = '<AppLockerPolicy Version="1"><RuleCollection ' + 'Type="Exe" EnforcementMode="NotConfigured"><FilePublisherRule ' + 'Name="test_filepublisher" Id="acd2e5a8-6b8f" Description="Test ' + 'file publisher rule set" Action="Deny" UserOrGroupSid="S-1-1-0">' + '<Conditions><FilePublisherCondition PublisherName="O=TEAMVIEWER ' + 'GMBH, L=GOPPINGEN, S=BADEN-WURTTEMBERG, C=DE" ProductName="*" ' + 'BinaryName="*"><BinaryVersionRange LowSection="*" HighSection=' + '"*"/></FilePublisherCondition></Conditions></FilePublisherRule>' + '</RuleCollection></AppLockerPolicy>' expect(builder.to_xml( :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION, ).strip).to eql(expected_policy) end end # Put all of the pieces together to generate the full policy context 'When converting XML to a Hash' do include CPE::Applocker before do mock_cpe_applocker_enable(true) mock_get_applocker_rules(true) end it 'should render a known Applocker state' do # First, render our test state state = xml_to_hash( Nokogiri::XML(applocker.gen_applocker_xml), ) # There should be 5 Configuration "states" expect(state.length).to eql(5) expect(state['Appx']['mode']).to eql('AuditOnly') expect(state['Exe']['mode']).to eql('Enforce') # Check the values we're parsing out for AppLocker rules = state['Exe']['rules'] expect(rules.length).to eql(3) # The first rule is a FilePath rule expect(rules[0]['name']).to eql('test_filepath') expect(rules[0]['type']).to eql('path') expect(rules[0]['id']).to eql('fd686d83-a829') expect(rules[0]['description']).to eql('Test file path rule set') expect(rules[0]['user_or_group_sid']).to eql('S-1-5-32-544') expect(rules[0]['action']).to eql('Allow') # We only have 1 path condition currently expect(rules[0]['conditions'].length).to eql(1) expect(rules[0]['conditions'][0]['path']).to eql('%OSDRIVE%\\tools\\*') # The second rule is a FileHash rule expect(rules[1]['name']).to eql('test_filehash') expect(rules[1]['type']).to eql('hash') expect(rules[1]['id']).to eql('f8b0f25b-776f') expect(rules[1]['description']).to eql('Test file hash rule set') expect(rules[1]['user_or_group_sid']).to eql('S-1-1-0') expect(rules[1]['action']).to eql('Deny') # We only have 1 hash condition currently expect(rules[1]['conditions'].length).to eql(1) expect(rules[1]['conditions'][0]['type']).to eql('SHA256') expect(rules[1]['conditions'][0]['file_name']).to eql('calc.exe') expect(rules[1]['conditions'][0]['file_length']).to eql('27648') expect(rules[1]['conditions'][0]['data']).to eql('0x267FEDEDD79AAAF7CF' + 'A62ECAA29BC0D8F5553FBD007A48BEC76C177BB7242CBC') # The third rule is a FilePublisher rule expect(rules[2]['name']).to eql('test_filepublisher') expect(rules[2]['type']).to eql('certificate') expect(rules[2]['id']).to eql('acd2e5a8-6b8f') expect(rules[2]['description']).to eql('Test file publisher rule set') expect(rules[2]['user_or_group_sid']).to eql('S-1-1-0') expect(rules[2]['action']).to eql('Deny') # We only have 1 certificate condition currently expect(rules[2]['conditions'].length).to eql(1) expect(rules[2]['conditions'][0]['binary_name']).to eql('*') expect(rules[2]['conditions'][0]['product_name']).to eql('*') expect(rules[2]['conditions'][0]['binary_version']['low']).to eql('*') expect(rules[2]['conditions'][0]['binary_version']['high']).to eql('*') expect(rules[2]['conditions'][0]['publisher']).to eql('O=TEAMVIEWER ' + 'GMBH, L=GOPPINGEN, S=BADEN-WURTTEMBERG, C=DE') end end end