Управление правами доступа к WMI через Puppet

В качестве предисловия


Основной задачей моей работы является поддержка парка железных и vm хостов — уже под 200 (а приходил было менше 100, эх, время бежит…) Поддерживаю все железо, а также сеть. Также на мне весь мониторинг (используем Opsview — сделан на ядре nagios), аггрегация логов (я внедрил Logstash, обалденное opensource решение за место ну ооочень дорогого Splunk), configuration management (puppet), бекапы, поддержка баз данных и прочих систем тоже на мне (MongoDB, MySQL, Redis, ElasticSearch, etc). В общем — все самое интересное). Стоит отметить что у нас достаточно тонкая грань между поддержкой и разработкой, и разработчики часто говорят что они хотят, а я уже занимаюсь внедрением. Хочется рассказать обо всем что происходит интересного и какие технологии удается использовать. Какие прижились, а какие по каким-то причинам нет.

В свободное от решения проблем время перевожу инфраструктуру на Infrastructure-as-a-code (IaaC), выбрал puppet для этого из-за неоднородности нашей инфраструктуры. В моей сети зоопарк из Windows Server 2008, Windows Server 2012, CentOS 5.5, CentOS 6.4. Ах, да, пару дедушек на 2003 — пора их на пенсию отправлять скоро…

Я уже писал о том, как я использую Puppet для автоматической настройки мониторинга в Opsvew, а сегодня хочу поговорить о том, как я в очередной раз «боролся» с гетерогенностью моей среды.

Задача

Возникла необходимость автоматизировать конфигурацию WMI на серверах Windows 2008 / 2012. Ключевой необходимостью стало добавление сервисного пользователя (назовем его «domain\service-user») в локальные группы сервера, которые разрешают удаленное использование WMI, а также доступ к Performance Counters, Performance Logs, в общем ко всему что нужно чтобы удаленно мониторить сервер. Сами группы определились достаточно быстро, оставалось найти удобный и быстрый способ это сделать. Также необходимо было дать права пользователю domain\service-user на доступ к корневым неймспейсам WMI. Так же все это должно быть частью общей концепции IaaC, что должно означать как минимум проверку текущего состояния, и пропускать выполнение если пользователь уже добавлен куда нужно в любом варианте присутствия-отсутствия пользователя в группах. Т.е. решение должно быть максимально автоматизированным, а точнее полностью. После небольшого гугления стало ясно что для моего случая нужно, а мне предстояло:

Добавить доменного пользователя domain\service-user в локальные группы (как минимум):
— Certificate Service DCOM Access
— Performance Log Users
— Performance Monitor Users
— Distributed COM Users.
Установить права доступа, как минимум, «read» для пользователя domain\service-user на следующие неймспейсы WMI:
— CIMV2
— MicrosoftIISv2.
Как только все будет установлено, check_wmi_plus (входит в стандартную поставку Opsview Pro) сможет получить необходимые данные об IIS и других интересных параметрах (а это то нам и надо)!

Сложности

Главная сложность которая возникла — готового решения чтобы запустить и быть уверенным в том, что «Сервер будет доступен для мониторинга» нет. В общем-то я и не очень расстроился, потому что очень редко бывают готовые решения под мои задачи, а если и бывают то часто делают что-то не то или не так.

Puppet имеет встроенный ресурс «user», который, по идее, должен был выполнить половину всех задач, не заработал в связке «доменный пользователь — локальная группа». Как оказалось это известный баг и его собираются исправлять (UPD: уже исправили в релизе 3.4), но постоянно отодвигают в следующий релиз puppet. Попытка выполнить workaround в puppet DSL не увенчалась успехом из-за слишком сложной структуры, требующей сложных escape последовательностей, которые не всегда работают.

Еще одна сложность — в windows отсутствует встроенный универсальный способ управления правами доступа к wmi классам, который можно было бы «обернуть» в puppet, если только ковырять реестр и изобретать велосипед.

Реализация

В итоге я принял решение написать свой провайдер и использовать его, до тех пор пока родной провайдер для добавления доменного пользователя в локальную группу на сервере не будет починен. И сделал я это… обернув powershell код!

win_user.rb

Puppet::Type.type(:win_user).provide(:win_user) do   @doc = %q{Manage windows users and groups}   desc "Manage windows users and groups"   confine    :operatingsystem => :windows   defaultfor :operatingsystem => :windows 	  	commands :powershell => 	if File.exists?("#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe") 	  "#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe" 	elsif File.exists?("#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe") 	  "#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe" 	else 	  'powershell.exe' 	end    def add_user_to_group     #   end    def exists?     #add transform to array if just a string          groups = resource[:groups]     if groups.kind_of?(String)       groups.to_a      end        found = false      groups.to_a.each do |group|       Puppet.debug("Checking the existence of value: #{self} in #{group}")       result = powershell 'if (([ADSI]"WinNT://$env:computername/'+group+'").IsMember(([ADSI]"WinNT://$env:userdomain/'+ resource[:name]+'").ADsPath) -eq $False){echo 0} else {echo 1}'                    if result.chomp == "0" #if not found (ps returned false on search of user in group)         Puppet.debug("User '#{resource[:name]}' is not found in group '#{group}'")         found = false #         break       else         Puppet.debug("User '#{resource[:name]}' is found in a group '#{group}'")         found = true       end     end     found   end    def create     groups = resource[:groups]     if groups.kind_of?(String)       groups.to_a      end      groups.to_a.each do |group|       Puppet.debug("Adding user to a group #{self}")       powershell 'if (([ADSI]"WinNT://$env:computername/'+group+'").IsMember(([ADSI]"WinNT://$env:userdomain/'+ resource[:name]+'").ADsPath) -eq $False){$([ADSI]"WinNT://$env:computername/'+group+'").Add(([ADSI]"WinNT://$env:userdomain/'+resource[:name]+'").ADsPath)} else {echo "User is already in a group"}'     end   end    def destroy        end end 

Для настройки параметров WMI пришлось пользоваться сторонней opensource утилитой wmisecurity.exe. Для ее установки я создал пакет на chocoaltey.org — wmisecurity. Для установки пакета я использовал puppet-провайдер chocolatey, который я использую постоянно.

И вот сам puppet-манифест, который использует раннее написанный модуль, а также содержит powershell хуки для добавления прав доступа к wmi классам для пользователя (возможно перепишу это как отдельный модуль позднее):

wmi.pp

class packages::wmi {	 	 	$wmiuser = 'service-user' 	 	###Doesn't work on windows right now 	#user { $wmiuser: 	#		groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'], 	#	}  	     win_user { $wmiuser:       groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'],       ensure => present_local,     }  	###it is required to add user to those local groups in order monitoring to perform correctly. 	 	exec {"add-to-wmi-cimv2": 		command => "wmisecurity.exe /C=\$env:computername /A /N=Root/CIMV2 /M=\$env:userdomain\\$wmiuser:REMOTEACCESS /R", 		path => $::path,		 		#if found user guid - skip 		onlyif => "if (WmiSecurity.exe /c=\$env:computername /N=Root/CIMV2 /R | Select-String $($(New-Object System.Security.Principal.NTAccount( 		\"\$env:userdomain\", '$wmiuser')).Translate([System.Security.Principal.SecurityIdentifier]).Value)){exit 1} else {exit 0}", 		provider => powershell, 		require => Package['wmisecurity'],	 	}	 	 	exec {"add-to-wmi-microsoftiisv2": 		command => "wmisecurity.exe /C=\$env:computername /A /N=Root/MicrosoftIISv2 /M=\$env:userdomain\\$wmiuser:REMOTEACCESS /R", 		path => $::path,		 		#if found user guid - skip 		onlyif => "if (WmiSecurity.exe /c=\$env:computername /N=Root/MicrosoftIISv2 /R | Select-String $($(New-Object System.Security.Principal.NTAccount( 		\"\$env:userdomain\", '$wmiuser')).Translate([System.Security.Principal.SecurityIdentifier]).Value)){exit 1} else {exit 0}", 		provider => powershell, 		require => Package['wmisecurity'],	 	} 	 	package {'wmisecurity': 		provider => 'chocolatey', 		install_options => '-pre',		 		require         => Class["packages::chocolatey"] 	} } 

Заключение

Конечно, сам модуль далек от идеала, и не хватает много чего, код явно грязный, но он работает и выполняет то, что задумывалось. Рефакторинг планируется в следующей итерации, и думаю это случится когда выйдет 3.4. Вот идеальный вариант манифеста, который я себе представляю (для тех, кто будет ругаться про «грязный код, который работает»):

wmi.pp

class packages::wmi {	 	 	$wmiuser = "${env:userdomain}\\service-user"	 	 	     user { $wmiuser:       groups => ['Certificate Service DCOM Access','Performance Log Users','Performance Monitor Users', 'Distributed COM Users'],       ensure => present,     } 	 	wmi_security_user { $wmiuser: 		namespaces => ['Root/CIMV2','Root/MicrosoftIISv2'], 		ensure => present, 	} } 

Теперь при настройке нового сервера мне остается назначить класс packages::wmi на этот сервер (в ручную или через include) и все, puppet сделает свое дело. Лично я чаще всего использую этот класс через класс opsview, который автоматически создает хост для мониторинга в opsview и назначет нужные шаблоны, т.е. если это, скажем, сервер c IIS, итоговый puppet-класс сообщит opsview всю необходимую информацию о том, что это хост с IIS, с такими-то и такими-то хостами которые нужно тоже мониторить определенным образом, а также назначит в opsview шаблон мониторинга через wmi, который зависит от класса который мы описали выше. Вот так, вроде ничего не упустил.

Пара скриншотов результата:


ссылка на оригинал статьи http://habrahabr.ru/post/204306/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *