⬆️ ⬇️

Managing WMI Access Rights with Puppet



As a preface



The main objective of my work is to support the fleet of iron and vm hosts - already under 200 (and it came to less than 100, oh, time is running ...) I support all hardware, as well as the network. Also, all monitoring is on me (using Opsview - made on the nagios core), log aggregation (I implemented Logstash, awesome opensource solution for a place well, sooo expensive Splunk), configuration management (puppet), backups, database support and other systems are also on me (MongoDB, MySQL, Redis, ElasticSearch, etc). In general - all the most interesting). It is worth noting that we have a rather thin line between support and development, and developers often say what they want, and I am already engaged in implementation. I want to talk about everything that happens interesting and what technologies can be used. Which got accustomed, and which for some reason do not.



In my spare time, I transfer infrastructure to Infrastructure-as-a-code (IaaC), chose puppet for this because of the heterogeneity of our infrastructure. In my network is a zoo from Windows Server 2008, Windows Server 2012, CentOS 5.5, CentOS 6.4. Oh yes, a couple of grandfathers for 2003 - it's time to send them to retire soon ...



I already wrote about how I use Puppet to automatically configure monitoring in Opsvew , and today I want to talk about how I once again "struggled" with the heterogeneity of my environment.



Task



There was a need to automate WMI configuration on Windows 2008/2012 servers. The key need was to add a service user (let's call it “domain \ service-user”) to local server groups that allow remote use of WMI, as well as access to Performance Counters, Performance Logs, in general to all that is needed to monitor the server remotely. The groups themselves were determined fairly quickly, it remained to find a convenient and fast way to do this. It was also necessary to give the domain \ service-user user rights to access WMI root namespaces. Also, all this should be part of the general concept of IaaC, which should mean at least checking the current state, and skip execution if the user is already added to where it is needed in any variant of the user’s presence or absence in groups. Those. the solution should be as automated as possible, or rather completely. After a little googling, it became clear what was needed for my case, but I had to:



Add domain user domain \ service-user to local groups (at a minimum):

- Certificate Service DCOM Access

- Performance Log Users

- Performance Monitor Users

- Distributed COM Users.

Set access rights, at a minimum, “read” for the domain \ service-user user to the following WMI namespaces:

- CIMV2

- MicrosoftIISv2.

Once everything is installed, check_wmi_plus (included in the standard Opsview Pro package) will be able to obtain the necessary data about IIS and other interesting parameters (and this is what we need)!

')

Difficulties



The main difficulty that has arisen is that there is no ready-made solution to launch and be sure that the “Server will be available for monitoring”. In general, I was not very upset, because very rarely there are ready-made solutions for my tasks, and if they are, they often do something wrong or wrong.



Puppet has a built-in resource “user”, which, in theory, should have completed half of all tasks, did not work in the “domain user - local group” bundle. As it turned out, this is a known bug and they are going to fix it ( UPD: they already fixed it in release 3.4 ), but they are constantly pushing puppet to the next release. An attempt to perform a workaround in puppet DSL was not successful due to an overly complex structure that requires complex escape sequences that do not always work.



Another complication is that in windows there is no built-in universal way to manage access rights to wmi classes, which could be “wrapped” in puppet, if you just pick the registry and reinvent the wheel.



Implementation



In the end, I decided to write my provider and use it, until my own provider to add the domain user to the local group on the server is repaired. And I did it ... by wrapping the powershell code!



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 




To configure the WMI parameters, I had to use the third-party opensource utility wmisecurity.exe. To install it, I created a package on chocoaltey.org - wmisecurity . To install the package, I used chocolatey puppet provider, which I use all the time.



And here is the puppet manifest that uses the previously written module, and also contains powershell hooks to add permissions to the wmi classes for the user (maybe I will rewrite this as a separate module later):



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"] } } 




Conclusion



Of course, the module itself is far from ideal, and a lot of things are missing, the code is clearly dirty, but it works and executes what was intended. Refactoring is planned for the next iteration, and I think this will happen when it comes out 3.4. Here is the ideal manifest that I imagine (for those who will swear about “dirty code that works”):



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, } } 




Now, when setting up a new server, it remains for me to assign the packages :: wmi class to this server (manually or via include) and everything puppet will do its job. Personally, I most often use this class through the opsview class, which automatically creates a host for monitoring in opsview and assigns the necessary templates, i.e. if, say, a server with IIS, the final puppet class will tell opsview all the necessary information that this is a host with IIS, with such and such hosts that need to be monitored in a certain way, and will also assign a monitoring pattern to opsview via wmi, which depends on the class we described above. So, like nothing missed.



A couple of screenshots of the result:




Source: https://habr.com/ru/post/204306/



All Articles