itchef/cookbooks/cpe_chrome/resources/cpe_chrome_win.rb (248 lines of code) (raw):

# 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. # Cookbook Name:: cpe_chrome # Resources:: cpe_chrome_win resource_name :cpe_chrome_win provides :cpe_chrome, :os => 'windows' default_action :config action :config do # check file path for Chrome since osquery doesn't detect # chrome is installed on all machines chrome_installed = ::File.file?( "#{ENV['ProgramFiles(x86)']}\\Google\\Chrome\\Application\\chrome.exe", ) || ::File.file?( "#{ENV['ProgramFiles']}\\Google\\Chrome\\Application\\chrome.exe", ) return unless chrome_installed || node.installed?('Google Chrome') return unless node['cpe_chrome']['profile'].values.any? if node['cpe_chrome']['_use_new_windows_provider'] reg_settings = [] node['cpe_chrome']['profile'].each do |setting_key, setting_value| next if setting_value.nil? setting = CPE::ChromeManagement::KnownSettings::GENERATED.fetch( setting_key, nil, ) unless setting Chef::Log.warn("#{setting_key} is not a known setting, skipping") next end if setting_value.is_a?(Hash) next if setting_value.empty? setting.value = setting_value.to_json else setting.value = setting_value end reg_settings << setting end # Set all the keys we care about. If there's any mismatch of data, just # delete the entire key and re-establish it, because we can't atomically # change individual subkeys in one single registry_key resource invocation CPE::ChromeManagement::KnownSettings::GENERATED.each do |name, obj| if obj.is_a?(WindowsChromeFlatSetting) begin current_values = registry_get_values(obj.registry_location). select { |k, _| name == k[:name] } rescue Chef::Exceptions::Win32RegKeyMissing next end next unless current_values.any? next unless obj.value.nil? next if current_values == obj.to_chef_reg_provider registry_key obj.registry_location do values current_values action :delete end elsif obj.is_a?(WindowsChromeIterableSetting) if registry_key_exists?(obj.registry_location) && obj.value.nil? registry_key obj.registry_location do recursive true action :delete_key end end end end reg_settings.each do |setting| registry_key setting.registry_location do values setting.to_chef_reg_provider recursive true action :create end end # Manage Extension Settings extprefs = node['cpe_chrome']['extension_profile'] if extprefs.empty? || extprefs.nil? registry_key CPE::ChromeManagement.chrome_reg_3rd_party_ext_root do recursive true action :delete_key end else # Loop through the extensions and create registry entries # Key path is HKLM\Software\Policies\Google\Chrome\3rdparty\extensions\EXT_ID\policy # https://www.chromium.org/administrators/configuring-policy-for-extensions extprefs.each do |k, v| ext_values = [] v['profile'].each do |k_ext, v_ext| ext_values << { 'name' => k_ext, 'type' => v_ext['windows_value_type'], 'data' => v_ext['value'], } end registry_key "#{CPE::ChromeManagement.chrome_reg_3rd_party_ext_root}\\#{k}\\policy" do values ext_values recursive true action :create end end end else # ExtensionSettings has a "dictionary" format and each key must be stored # as a separate sub key inside the ExtensionSettings registry setting # Ref: https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExtensionSettings extension_settings_key = 'ExtensionSettings'.freeze reg_settings = [] node['cpe_chrome']['profile'].each do |setting_key, setting_value| next if setting_value.nil? if setting_value.is_a?(Hash) next if setting_value.empty? end # ExtensionSettings must have a different registry structure if (setting_value.is_a? Hash) && (setting_key == extension_settings_key) setting_value.each do |extension_key, extension_value| if extension_value.is_a? Hash extension_value.each do |inner_key, inner_value| reg_settings << WindowsChromeSetting.new( { inner_key => inner_value }, "#{setting_key}\\#{extension_key}", ) end else reg_settings << WindowsChromeSetting.new( { extension_key => extension_value }, setting_key.to_s, ) end end else reconstruct_setting = { setting_key => setting_value } reg_settings << WindowsChromeSetting.new(reconstruct_setting) end end # Set all the keys we care about. If there's any mismatch of data, just # delete the entire key and re-establish it, because we can't atomically # change individual subkeys in one single registry_key resource invocation reg_settings.uniq.each do |setting| next if setting.fullpath.empty? new_values = setting.to_chef_reg_provider current_values = nil if registry_key_exists?(setting.fullpath) current_values = registry_get_values(setting.fullpath) end # Make sure we're comparing apples to apples, as registry_get_values() # always returns an array, even if the data itself only contains one value unless new_values.is_a?(Array) new_values = [new_values] end if new_values.empty? || new_values != current_values Chef::Log.debug( "cpe_chrome: Deleting #{setting.fullpath} because of mismatch", ) Chef::Log.debug("cpe_chrome: Old values: #{current_values}") Chef::Log.debug("cpe_chrome: New values: #{new_values}") registry_key setting.fullpath do recursive true action :delete_key end end registry_key setting.fullpath do not_if { new_values.empty? } values new_values recursive true action :create end end extprefs = node['cpe_chrome']['extension_profile'] unless extprefs.empty? || extprefs.nil? # Loop through the extensions and create registry entries # Key path is HKLM\Software\Policies\Google\Chrome\3rdparty\extensions\EXT_ID\policy # https://www.chromium.org/administrators/configuring-policy-for-extensions extprefs.each do |k, v| ext_values = [] v['profile'].each do |k_ext, v_ext| ext_values << { 'name' => k_ext, 'type' => v_ext['windows_value_type'], 'data' => v_ext['value'], } end registry_key "#{CPE::ChromeManagement.chrome_reg_3rd_party_ext_root}\\#{k}\\policy" do values ext_values recursive true action :create end end end # Look at all the subkeys total of the root Chrome extension key. all_chrome_ext_keys = [] extension_profile = node['cpe_chrome']['extension_profile'] if registry_key_exists?(CPE::ChromeManagement.chrome_reg_3rd_party_ext_root) && registry_has_subkeys?(CPE::ChromeManagement.chrome_reg_3rd_party_ext_root) all_chrome_ext_keys = registry_get_subkeys(CPE::ChromeManagement.chrome_reg_3rd_party_ext_root) end # This variable should be a superset (or a match) to the list of keys # in the node attribute. extra_chrome_ext_keys = all_chrome_ext_keys - extension_profile.keys Chef::Log.debug("#{cookbook_name}: Extra keys: #{extra_chrome_ext_keys}") extra_chrome_ext_keys.each do |rip_key| registry_key "#{CPE::ChromeManagement.chrome_reg_3rd_party_ext_root}\\#{rip_key}" do action :delete_key recursive true end end # Hack - if any extension profile configs, don't delete the root key chrome_profile = node['cpe_chrome']['profile'].to_h if extension_profile.values.any? chrome_profile['3rdparty'] = nil end # Look at all the subkeys total of the root Chrome key. all_chrome_keys = [] if registry_key_exists?(CPE::ChromeManagement.chrome_reg_root) && registry_has_subkeys?(CPE::ChromeManagement.chrome_reg_root) all_chrome_keys = registry_get_subkeys(CPE::ChromeManagement.chrome_reg_root) end # This variable should be a superset (or a match) to the list of keys # in the node attribute. extra_chrome_keys = all_chrome_keys - chrome_profile.keys extra_chrome_keys.each do |rip_key| registry_key "#{CPE::ChromeManagement.chrome_reg_root}\\#{rip_key}" do action :delete_key recursive true end end end # There are two migrations going on here. From the legacy x86 # program files to the x86_64 one, and from master_preferences to # initial_preferences. At time of writing, we have Chrome instances # installed in both locations. While we probably do not have any # chromes old enough to be unaware of the new initial_preferences # file name, we might, and the current files use that name. It's # safer to just keep creating it until we're sure everything is on # the new name. ['initial_preferences', 'master_preferences'].each do |basename| [ 'C:\\Program Files (x86)\\', 'C:\\Program Files\\', ].each do |prefix| pref_path = "#{prefix}\\Google\\Chrome\\Application\\#{basename}" file "delete-#{pref_path}" do only_if do node['cpe_chrome']['mp']['FileContents']. to_hash. reject { |_k, v| v.nil? }. empty? end path pref_path action :delete end [ "#{prefix}\\Google", "#{prefix}\\Google\\Chrome", "#{prefix}\\Google\\Chrome\\Application", ].each do |dir| directory dir do # ~FB024 rights :read, 'Everyone', :applies_to_children => true rights :read_execute, 'Users', :applies_to_children => true rights :full_control, ['Administrators', 'SYSTEM'], :applies_to_children => true action :create end end file "create-#{pref_path}" do # ~FB023 not_if do node['cpe_chrome']['mp']['FileContents']. to_hash. reject { |_k, v| v.nil? }. empty? end content lazy { Chef::JSONCompat.to_json_pretty( node['cpe_chrome']['mp']['FileContents']. to_hash. reject { |_k, v| v.nil? }, ) } path pref_path rights :read, 'Everyone', :applies_to_children => true rights :read_execute, 'Users', :applies_to_children => true rights :full_control, ['Administrators', 'SYSTEM'], :applies_to_children => true action :create end end end end