update remote management library to provide methods for determining the current state of the tcc db, ARD agent, and ARD user priviges

This commit is contained in:
Jared Weyer 2023-03-09 09:29:05 -08:00
Родитель fffac938a9
Коммит 96d2d55eac
1 изменённых файлов: 202 добавлений и 21 удалений

Просмотреть файл

@ -1,35 +1,216 @@
include Chef::Mixin::ShellOut
module MacOS
class RemoteManagement
class << self
# macos::remote_management load_current_value helper
# the privileges property will be an integer mask
# compare this to the current mask of each user specified in the users property.
# Return empty string if any user still needs to set up privileges. The resource will converge.
# Return empty string if users have different masks. The resource will converge.
# Return an integer mask if the users share the same mask. The resource will NOT converge if the desired mask matches this mask.
def current_mask(*users)
configured_for_all_users? ? current_global_mask(users) : current_user_masks(users)
end
def current_global_mask(users)
global_settings_privilege_value = global_settings&.[]('ARD_AllLocalUsersPrivs')
global_settings_privilege_value.nil? ? current_user_masks(users) : BitMask.mask_from_value(global_settings_privilege_value)
end
def current_user_masks(users)
all_user_masks_hash = individual_settings
specified_user_masks = users.include?('all') ? all_user_masks_hash.values : users.flatten.map { |user| all_user_masks_hash.fetch(user) }
specified_user_masks.uniq.one? ? specified_user_masks.first : ''
rescue KeyError
''
end
def current_computer_info
computer_info_hash.select { |k, _v| k.match?(Regexp.new('Text')) }.values.reject(&:empty?)
end
def activated?
::File.exist? '/Library/Application Support/Apple/Remote Desktop/RemoteManagement.launchd'
agent_running? && correct_tccstate?
end
def configured_for_all_users_and_privileges?
RemoteManagement.plist_content.include?('ARD_AllLocalUsers = true') &&
RemoteManagement.plist_content.include?(full_privileges)
def agent_running?
::File.exist?('/Library/Application Support/Apple/Remote Desktop/RemoteManagement.launchd')
end
def plist_content
shell_out('/usr/libexec/PlistBuddy -c Print /Library/Preferences/com.apple.RemoteManagement.plist').stdout
def correct_tccstate?
post_event_service_tccstate_enabled? && screencapture_service_tccstate_enabled?
end
def full_privileges
text_messages = 1 << 0
control_observe = 1 << 1
send_files = 1 << 2
delete_files = 1 << 3
generate_reports = 1 << 4
open_quit_apps = 1 << 5
change_settings = 1 << 6
restart_shutdown = 1 << 7
show_observe = 1 << 30
def post_event_service_tccstate_enabled?
tccstate&.[]('postEvent')
end
(text_messages | control_observe | send_files |
delete_files | generate_reports | open_quit_apps |
change_settings | restart_shutdown | show_observe).to_s
def screencapture_service_tccstate_enabled?
tccstate&.[]('screenCapture')
end
def correct_tcc_db_privileges?
screensharing_client_authorized_for_post_event_service? && screensharing_client_authorized_for_screencapture_service?
end
def screensharing_client_authorized_for_post_event_service?
screensharing_client_auth_value_for_post_event_service == 2
end
def screensharing_client_authorized_for_screencapture_service?
screensharing_client_auth_value_for_screencapture_service == 2
end
def kickstart
::File.join(ard_agent_app_contents_path, 'Resources', 'kickstart')
end
private
def configured_for_all_users?
global_settings&.[]('ARD_AllLocalUsers')
end
def ard_agent_app_contents_path
'/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/'
end
def tccstate
::Plist.parse_xml(tccstate_xml)
end
def tccstate_xml
shell_out!('sudo', ::File.join(ard_agent_app_contents_path, 'Support', 'tccstate')).stdout
end
def global_settings
::Plist.parse_xml(global_settings_xml)
end
def global_settings_xml
shell_out!('/usr/bin/plutil -convert xml1 /Library/Preferences/nasgul.apple.RemoteManagement.plist -o -').stdout
rescue Mixlib::ShellOut::ShellCommandFailed => e
e.message.match?(Regexp.new('file does not exist')) ? '' : raise
end
def individual_settings
dscl_naprivs.scan(/(?<user>^\S+)\s+(?<mask>-\d+)/).to_h.transform_values(&:to_i)
end
def dscl_naprivs
shell_out!('/usr/bin/dscl . -list /Users dsAttrTypeNative:naprivs').stdout
end
def tcc_db_path
'/Library/Application Support/com.apple.TCC/TCC.db'
end
def screensharing_client_auth_value_for_post_event_service
shell_out!('sqlite3', tcc_db_path, "SELECT auth_value FROM access WHERE service='kTCCServicePostEvent' AND client='com.apple.screensharing.agent';").stdout.chomp.to_i
end
def screensharing_client_auth_value_for_screencapture_service
shell_out!('sqlite3', tcc_db_path, "SELECT auth_value FROM access WHERE service='kTCCServiceScreenCapture' AND client='com.apple.screensharing.agent';").stdout.chomp.to_i
end
def computer_info_xml
shell_out!('/usr/bin/plutil -convert xml1 /Library/Preferences/com.apple.RemoteDesktop.plist -o -').stdout
rescue Mixli::ShellOut::ShellCommandFailed => e
e.message.match?(Regexp.new('file does not exist')) ? '' : raise
end
def computer_info_hash
::Plist.parse_xml(computer_info_xml)
end
end
module BitMask
TEXT_MESSAGES = 0b00000000000000000000000000000001
CONTROL_OBSERVE = 0b00000000000000000000000000000010
SEND_FILES = 0b00000000000000000000000000000100
DELETE_FILES = 0b00000000000000000000000000001000
GENERATE_REPORTS = 0b00000000000000000000000000010000
OPEN_QUIT_APPS = 0b00000000000000000000000000100000
CHANGE_SETTINGS = 0b00000000000000000000000001000000
RESTART_SHUT_DOWN = 0b00000000000000000000000010000000
OBSERVE_ONLY = 0b00000000000000000000000100000000
SHOW_OBSERVE = 0b01000000000000000000000000000000
ALL = 0b01000000000000000000000011111111
NONE = 0b10000000000000000000000000000000
class << self
def valid_mask?(mask)
value = value_from_mask(mask)
return true if value == NONE
(~(ALL | OBSERVE_ONLY) & value).zero?
end
def valid_privileges?(privileges)
invalid_privileges(privileges).empty?
end
def invalid_privileges(privileges)
privileges - constants(false)
end
def validate_privileges!(privileges)
raise RemoteManagement::Exceptions::BitMask::PrivilegeValidationError unless valid_privileges?(privileges)
rescue RemoteManagement::Exceptions::BitMask::PrivilegeValidationError # raise property validation error if called from propery coersion block
called_by_chef_property_coerce ? raise(Chef::Exceptions::ValidationFailed, "Option privilege's value is invalid. See https://ss64.com/osx/kickstart.html for valid privileges!") : raise
end
def called_by_chef_property_coerce
caller_locations.any? { |backtrace_location| ::File.basename(backtrace_location.path, '.rb') == 'property' && backtrace_location.label == 'coerce' }
end
def value_from_privileges(privileges)
privileges = format_privileges(privileges)
validate_privileges!(privileges)
return ALL if privileges.include?(:ALL)
return 0 if privileges.include?(:NONE)
privileges.map { |priv| const_get(priv.upcase) }.reduce(&:|)
end
def format_privileges(privileges)
[privileges].flatten.map { |privilege| privilege.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').gsub(/(\s|-|_{2,})/, '_').upcase.to_sym }
end
def mask_from_value(value)
value - NONE
end
def value_from_mask(mask)
mask + NONE
end
def mask_from_privileges(privileges)
value = value_from_privileges(privileges)
mask_from_value(value)
end
end
end
module Exceptions
class TCCError < RuntimeError
def initialize
super(message)
end
def message
String.new.tap do |message|
message << "TCC does not have the correct authorizations for ARD to work!\n"
message << "\t* Screensharing client not authorized for post event service\n" unless RemoteManagement.screensharing_client_authorized_for_post_event_service?
message << "\t* Screensharing client not authorized for screencapture service\n" unless RemoteManagement.screensharing_client_authorized_for_screencapture_service?
end
end
end
module BitMask
class PrivilegeValidationError < ArgumentError
def initialize
super("recieved invalid privilege! Valid privileges include: #{RemoteManagement::BitMask.constants(false).map(&:downcase).map(&:to_s)}")
end
end
end
end
end