itchef/cookbooks/cpe_applocker/libraries/applocker_helpers.rb (202 lines of code) (raw):
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# Cookbook Name:: cpe_applocker
#
# 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.
#
module CPE
module Applocker
# Statically defined types that we support for applocker rules
APPLOCKER_TYPE_MAP ||= {
'FilePathRule' => 'path',
'FileHashRule' => 'hash',
'FilePublisherRule' => 'certificate',
}.freeze
def self.gen_deterministic_uuid(seed)
uuid = (Digest::SHA256.hexdigest seed).split(//).last(32).join
pos = [8, 13, 18, 23]
uuid = pos.map { |n| uuid = uuid.insert(n, '-') }[-1]
uuid
end
def get_applocker_rules
@applocker_rules ||= node['cpe_applocker']['applocker_rules'].dup
end
def cpe_applocker_enabled?
@cpe_applocker_enabled ||= node['cpe_applocker']['enabled']
end
def set_applocker_policy
powershell_script 'Apply updated Applocker configuration' do
# not all data being passed in via the API is valid so this can fail.
# skipping over errors for this until a proper maintainer is found
ignore_failure true
code <<-EOH
# Ensure that the assemblies for managing Applocker are loaded, without
# this we cannot load the AppLockerPolicy rendering functions used below
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel') | Out-Null
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.AppLockerPolicy]::FromXml(
([xml]'#{gen_applocker_xml}').outerXML
) | Set-ApplockerPolicy
EOH
end
end
def clear_applocker_policy
powershell_script 'Remove all AppLocker rules' do
not_if <<-EOH
# Result starts at False.
$result = $True
# Iterate over each collection.
(Get-AppLockerPolicy -Local).RuleCollections | ForEach {
# If any collection is not empty (e.g. $False) the -And will make
# all future $result return $false.
# Result will only ever return $True if all items are empty
$result = $result -And [string]::IsNullOrEmpty($_)
}
$result
EOH
# To disable applocker we should remove the policies.
code <<-EOH
# Need to call this next line or else the script times out
$null = Get-AppLockerPolicy -Local -ErrorAction SilentlyContinue
$TempFile = [System.IO.Path]::GetTempFileName()
Set-Content -Path $TempFile -Value '<AppLockerPolicy Version="1">
<RuleCollection Type="Exe" EnforcementMode="NotConfigured" />
<RuleCollection Type="Msi" EnforcementMode="NotConfigured" />
<RuleCollection Type="Script" EnforcementMode="NotConfigured" />
<RuleCollection Type="Dll" EnforcementMode="NotConfigured" />
<RuleCollection Type="Appx" EnforcementMode="NotConfigured" />
</AppLockerPolicy>'
Set-ApplockerPolicy -XMLPolicy $TempFile
Remove-Item -Force $TempFile
EOH
end
end
# Given an XML config from AppLocker, this should return a Hash with only
# the values that we care about.
def xml_to_hash(xml)
policy = {
'Appx' => { 'rules' => [] },
'Dll' => { 'rules' => [] },
'Exe' => { 'rules' => [] },
'Msi' => { 'rules' => [] },
'Script' => { 'rules' => [] },
}
xml.root.children.each do |elem|
next unless elem.is_a?(Nokogiri::XML::Element)
policy[elem['Type']]['mode'] = elem['EnforcementMode']
# First get the metadata about each rule
elem.children.each do |rule|
# We only process rules we have definitions for
next unless APPLOCKER_TYPE_MAP.key? rule.node_name
type = APPLOCKER_TYPE_MAP[rule.name]
pol_rule = {
'type' => type,
'name' => rule['Name'],
'id' => rule['Id'],
'action' => rule['Action'],
'description' => rule['Description'],
'user_or_group_sid' => rule['UserOrGroupSid'],
}
conditions = []
# each rule has a `Condition` section, and there may be more than 1
rule.children.each do |app_conditions|
app_conditions.children.each do |condition|
case type
when 'path'
next if condition['Path'].nil?
conditions << { 'path' => condition['Path'] }
when 'hash'
condition.children.each do |filehash|
next if filehash['Data'].nil?
conditions << {
'type' => filehash['Type'],
'data' => filehash['Data'],
'file_name' => filehash['SourceFileName'],
'file_length' => filehash['SourceFileLength'],
}
end
when 'certificate'
next if condition['PublisherName'].nil?
bin_publisher = {
'publisher' => condition['PublisherName'],
'product_name' => condition['ProductName'],
'binary_name' => condition['BinaryName'],
}
# Get all of the binary version information
condition.children.each do |binver|
next if binver['LowSection'].nil?
bin_publisher['binary_version'] = {}
bin_publisher['binary_version']['low'] = binver['LowSection']
bin_publisher['binary_version']['high'] = binver[
'HighSection'
]
end
conditions << bin_publisher
end
end
end
pol_rule['conditions'] = conditions
# And add the constructed rule to our ruleset
policy[elem['Type']]['rules'] << pol_rule
end
end
policy
end
def gen_file_path_rule(rule, xml)
xml.FilePathRule(:Name => rule['name'],
:Id => rule['id'],
:Description => rule['description'],
:Action => rule['action'],
:UserOrGroupSid => rule['user_or_group_sid']) do
xml.Conditions do
rule['conditions'].each do |condition|
xml.FilePathCondition(:Path => condition['path'])
end # End of Each FilePathCondition
end # End of Conditions
end # End of FilePathRule
end
def gen_file_hash_rule(rule, xml)
xml.FileHashRule(:Name => rule['name'],
:Id => rule['id'],
:Description => rule['description'],
:Action => rule['action'],
:UserOrGroupSid => rule['user_or_group_sid']) do
xml.Conditions do
rule['conditions'].each do |condition|
xml.FileHashCondition do
xml.FileHash(:Type => condition['type'],
:Data => condition['data'],
:SourceFileName => condition['file_name'],
:SourceFileLength => condition['file_length'])
end
end # End of FileHashCondition
end # End of Conditions
end # End of FileHashRule
end
def gen_file_publisher_rule(rule, xml)
xml.FilePublisherRule(:Name => rule['name'],
:Id => rule['id'],
:Description => rule['description'],
:Action => rule['action'],
:UserOrGroupSid => rule['user_or_group_sid']) do
xml.Conditions do
rule['conditions'].each do |condition|
xml.FilePublisherCondition(:PublisherName =>
condition['publisher'],
:ProductName =>
condition['product_name'],
:BinaryName =>
condition['binary_name']) do
xml.BinaryVersionRange(
:LowSection => condition['binary_version']['low'],
:HighSection => condition['binary_version']['high'],
) # End of BinaryVersionRange
end # End of FilePublisherCondition
end # End of Each file publisher condition
end # End of Conditions
end # End of FilePublisherRule
end
# Helper function to generate our XML configuration file
def gen_applocker_xml
require 'nokogiri'
# Generic XML builder function. When enabled, this produces our
# XML for enabling the Applocker service. When disabled, this produces
# a "clean" XML policy for Applocker configuration
builder = Nokogiri::XML::Builder.new do |xml|
xml.AppLockerPolicy(:Version => 1) do
# Add to it each of our elements
get_applocker_rules.map do |ruleset, opts|
xml.RuleCollection(:Type => ruleset,
:EnforcementMode =>
cpe_applocker_enabled? ? opts['mode'] :
'NotConfigured') do
opts['rules'].each do |rule|
case rule['type']
when 'path'
gen_file_path_rule(rule, xml)
when 'hash'
gen_file_hash_rule(rule, xml)
when 'certificate'
gen_file_publisher_rule(rule, xml)
end
end # opts['rules']
end # End of RuleCollection
end # End of Each Applocker ruleset, opts
end # End of AppLockerPolicy
end # End of Xml Builder
builder.to_xml(
:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML |
Nokogiri::XML::Node::SaveOptions::NO_DECLARATION,
).strip
end
end
end