Skip to content

Commit b31cd04

Browse files
authored
Merge pull request #149 from Icinga/feature/add_wmi_permission_setter
Feature: Adds support to add/remove/test Wmi permissions You can now use 'Add-IcingaWmiPermissions' to add permissions for a specific user and namespace and remove them with 'Remove-IcingaWmiPermissions'
2 parents 3ca5f44 + f9f095e commit b31cd04

11 files changed

+566
-16
lines changed

doc/31-Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
1818
* [#139](https://github.com/Icinga/icinga-powershell-framework/pull/139) Add Cmdlet `Start-IcingaShellAsUser` to open an Icinga Shell as different user for testing
1919
* [#141](https://github.com/Icinga/icinga-powershell-framework/pull/141) Adds Cmdlet `Convert-IcingaPluginThresholds` as generic approach to convert Icinga Thresholds with units to the lowest unit of this type.
2020
* [#134](https://github.com/Icinga/icinga-powershell-framework/pull/134) Adds Cmdlet `Test-IcingaWindowsInformation` to check if a WMI class exist and if we can fetch data from it. In addition we add support for binary value comparison with the new Cmdlet `Test-IcingaBinaryOperator`
21+
* [#149](https://github.com/Icinga/icinga-powershell-framework/pull/149) Adds support to add Wmi permissions for a specific user and namespace with `Add-IcingaWmiPermissions`. In addition you can remove users from Wmi namespaces by using `Remove-IcingaWmiPermissions`
2122

2223
### Bugfixes
2324

lib/core/tools/Get-IcingaUserSID.psm1

+2-12
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,10 @@ function Get-IcingaUserSID()
88
$User = 'NT Authority\SYSTEM';
99
}
1010

11-
[string]$Username = '';
12-
[string]$Domain = '';
13-
14-
if ($User.Contains('\')) {
15-
$TmpArray = $User.Split('\');
16-
$Domain = $TmpArray[0];
17-
$Username = $TmpArray[1];
18-
} else {
19-
$Domain = Get-IcingaNetbiosName;
20-
$Username = $User;
21-
}
11+
$UserData = Split-IcingaUserDomain -User $User;
2212

2313
try {
24-
$NTUser = New-Object System.Security.Principal.NTAccount($Domain, $Username);
14+
$NTUser = New-Object System.Security.Principal.NTAccount($UserData.Domain, $UserData.User);
2515
$SecurityData = $NTUser.Translate([System.Security.Principal.SecurityIdentifier]);
2616
} catch {
2717
throw $_.Exception;
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<#
2+
.SYNOPSIS
3+
Splits a username containing a domain into a hashtable to easily use both values independently.
4+
If no domain is specified the hostname will used as "local domain"
5+
.DESCRIPTION
6+
Splits a username containing a domain into a hashtable to easily use both values independently.
7+
If no domain is specified the hostname will used as "local domain"
8+
.PARAMETER User
9+
A user object either containing only the user or domain information
10+
.EXAMPLE
11+
PS>Split-IcingaUserDomain -User 'icinga';
12+
13+
Name Value
14+
---- -----
15+
User icinga
16+
Domain icinga-win
17+
.EXAMPLE
18+
PS>Split-IcingaUserDomain -User 'ICINGADOMAIN\icinga';
19+
20+
Name Value
21+
---- -----
22+
User icinga
23+
Domain ICINGADOMAIN
24+
.EXAMPLE
25+
PS>Split-IcingaUserDomain -User 'icinga@ICINGADOMAIN';
26+
27+
Name Value
28+
---- -----
29+
User icinga
30+
Domain ICINGADOMAIN
31+
.EXAMPLE
32+
PS>Split-IcingaUserDomain -User '.\icinga';
33+
34+
Name Value
35+
---- -----
36+
User icinga
37+
Domain icinga-win
38+
.INPUTS
39+
System.String
40+
.OUTPUTS
41+
System.Hashtable
42+
#>
43+
44+
function Split-IcingaUserDomain()
45+
{
46+
param (
47+
$User
48+
);
49+
50+
if ([string]::IsNullOrEmpty($User)) {
51+
Write-IcingaConsoleError 'Please enter a valid username';
52+
return '';
53+
}
54+
55+
[array]$UserData = @();
56+
57+
if ($User.Contains('\')) {
58+
$UserData = $User.Split('\');
59+
} elseif ($User.Contains('@')) {
60+
[array]$Split = $User.Split('@');
61+
$UserData = @(
62+
$Split[1],
63+
$Split[0]
64+
);
65+
} else {
66+
$UserData = @(
67+
(Get-IcingaNetbiosName),
68+
$User
69+
);
70+
}
71+
72+
if ([string]::IsNullOrEmpty($UserData[0]) -Or $UserData[0] -eq '.' -Or $UserData[0] -eq 'BUILTIN') {
73+
$UserData[0] = (Get-IcingaNetbiosName);
74+
}
75+
76+
return @{
77+
'Domain' = $UserData[0];
78+
'User' = $UserData[1];
79+
};
80+
}

lib/wmi/Add-IcingaWmiPermissions.psm1

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<#
2+
.SYNOPSIS
3+
Sets permissions for a specific Wmi namespace for a user. You can grant basic permissions based
4+
on the arguments available and grant additional ones with the `-Flags` argument.
5+
.DESCRIPTION
6+
Sets permissions for a specific Wmi namespace for a user. You can grant basic permissions based
7+
on the arguments available and grant additional ones with the `-Flags` argument.
8+
.PARAMETER User
9+
The user to set permissions for. Can either be a local or domain user
10+
.PARAMETER Namespace
11+
The Wmi namespace to grant permissions for. Required namespaces are listed within each plugin documentation
12+
.PARAMETER Enable
13+
Enables the account and grants the user read permissions. This is a default access right for all users and corresponds to the Enable Account permission on the Security tab of the WMI Control. For more information, see Setting Namespace Security with the WMI Control.
14+
.PARAMETER RemoteAccess
15+
Allows a user account to remotely perform any operations allowed by the permissions described above. Only members of the Administrators group have this right. WBEM_REMOTE_ACCESS corresponds to the Remote Enable permission on the Security tab of the WMI Control.
16+
.PARAMETER Recurse
17+
Applies a container inherit flag and grants permission not only on the specific Wmi tree but also objects within this namespace (recommended)
18+
.PARAMETER DenyAccess
19+
Blocks the user from having access to this Wmi and or subnamespace tree.
20+
.PARAMETER Flags
21+
Allows to specify additional flags for permssion granting: PartialWrite, Subscribe, ProviderWrite,ReadSecurity, WriteSecurity, Publish, MethodExecute, FullWrite
22+
.EXAMPLE
23+
PS>Add-IcingaWmiPermissions -Namespace 'root\cimv2' -User icinga -Enable;
24+
.EXAMPLE
25+
PS>Add-IcingaWmiPermissions -Namespace 'root\cimv2' -User 'ICINGADOMAIN\icinga' -Enable -RemoteAccess;
26+
.EXAMPLE
27+
PS>Add-IcingaWmiPermissions -Namespace 'root\cimv2' -User 'ICINGADOMAIN\icinga' -Enable -RemoteAccess -Recurse;
28+
.EXAMPLE
29+
PS>Add-IcingaWmiPermissions -Namespace 'root\cimv2' -User 'ICINGADOMAIN\icinga' -Enable -RemoteAccess -Flags 'ReadSecurity', 'MethodExecute' -Recurse;
30+
.INPUTS
31+
System.String
32+
.OUTPUTS
33+
System.Boolean
34+
#>
35+
36+
function Add-IcingaWmiPermissions()
37+
{
38+
param (
39+
[string]$User,
40+
[string]$Namespace,
41+
[switch]$Enable,
42+
[switch]$RemoteAccess,
43+
[switch]$Recurse,
44+
[switch]$DenyAccess,
45+
[array]$Flags
46+
);
47+
48+
if ([string]::IsNullOrEmpty($User)) {
49+
Write-IcingaConsoleError 'Please enter a valid username';
50+
return $FALSE;
51+
}
52+
53+
if ([string]::IsNullOrEmpty($Namespace)) {
54+
Write-IcingaConsoleError 'You have to specify a Wmi namespace to grant permissions for';
55+
return $FALSE;
56+
}
57+
58+
[int]$PermissionMask = New-IcingaWmiPermissionMask -Enable:$Enable -RemoteAccess:$RemoteAccess -Flags $Flags;
59+
60+
if ($PermissionMask -eq 0) {
61+
Write-IcingaConsoleError 'You have to specify permissions to grant for a specific user';
62+
return $FALSE;
63+
}
64+
65+
if (Test-IcingaWmiPermissions -User $User -Namespace $Namespace -Enable:$Enable -RemoteAccess:$RemoteAccess -Recurse:$Recurse -DenyAccess:$DenyAccess -Flags $Flags) {
66+
Write-IcingaConsoleNotice 'Wmi permissions for user "{0}" are already set.' -Objects $User;
67+
return $TRUE;
68+
} else {
69+
Write-IcingaConsoleNotice 'Removing possible existing configuration for this user before continuing';
70+
Remove-IcingaWmiPermissions -User $User -Namespace $Namespace | Out-Null;
71+
}
72+
73+
$WmiSecurity = Get-IcingaWmiSecurityData -User $User -Namespace $Namespace;
74+
75+
if ($null -eq $WmiSecurity) {
76+
return $FALSE;
77+
}
78+
79+
$WmiAce = (New-Object System.Management.ManagementClass("Win32_Ace")).CreateInstance();
80+
$WmiAce.AccessMask = $PermissionMask;
81+
82+
if ($Recurse) {
83+
$WmiAce.AceFlags = $IcingaWBEM.AceFlags.Container_Inherit;
84+
} else {
85+
$WmiAce.AceFlags = 0;
86+
}
87+
88+
$WmiTrustee = (New-Object System.Management.ManagementClass("Win32_Trustee")).CreateInstance();
89+
$WmiTrustee.SidString = Get-IcingaUserSID -User $User;
90+
$WmiAce.Trustee = $WmiTrustee
91+
92+
if ($DenyAccess) {
93+
$WmiAce.AceType = $IcingaWBEM.AceFlags.Access_Denied;
94+
} else {
95+
$WmiAce.AceType = $IcingaWBEM.AceFlags.Access_Allowed;
96+
}
97+
98+
$WmiSecurity.WmiAcl.DACL += $WmiAce.PSObject.immediateBaseObject;
99+
100+
$WmiSecurity.WmiArguments.Name = 'SetSecurityDescriptor';
101+
$WmiSecurity.WmiArguments.Add('ArgumentList', $WmiSecurity.WmiAcl.PSObject.immediateBaseObject);
102+
$WmiArguments = $WmiSecurity.WmiArguments;
103+
104+
$WmiSecurityData = Invoke-WmiMethod @WmiArguments;
105+
if ($WmiSecurityData.ReturnValue -ne 0) {
106+
Write-IcingaConsoleError 'Failed to set Wmi security descriptor information with error {0}' -Objects $WmiSecurityData.ReturnValue;
107+
return $FALSE;
108+
}
109+
110+
Write-IcingaConsoleNotice 'Wmi permissions for Namespace "{0}" and user "{1}" have been set successfully' -Objects $Namespace, $User;
111+
112+
return $TRUE;
113+
}

lib/core/framework/Get-IcingaWindowsInformation.psm1 renamed to lib/wmi/Get-IcingaWindowsInformation.psm1

+31
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
1+
<#
2+
.SYNOPSIS
3+
Allows to query Wmi information by either using Wmi directly or Cim. This provides a save handling
4+
to call Wmi classes, as we are catching possible errors including missing permissions for better
5+
and improved error output during plugin execution.
6+
.DESCRIPTION
7+
Allows to query Wmi information by either using Wmi directly or Cim. This provides a save handling
8+
to call Wmi classes, as we are catching possible errors including missing permissions for better
9+
and improved error output during plugin execution.
10+
.PARAMETER ClassName
11+
The Wmi class to fetch information from
12+
.PARAMETER Filter
13+
Allows to filter only for specific Wmi information. The syntax is identical to Get-WmiObject and Get-CimInstance
14+
.PARAMETER Namespace
15+
The Wmi namespace to lookup additional information. The syntax is identical to Get-WmiObject and Get-CimInstance
16+
.PARAMETER ForceWMI
17+
Forces the usage of `Get-WmiObject` instead of `Get-CimInstance`
18+
.EXAMPLE
19+
PS>Get-IcingaWindowsInformation -ClassName Win32_Service;
20+
.EXAMPLE
21+
PS>Get-IcingaWindowsInformation -ClassName Win32_Service -ForceWMI;
22+
.EXAMPLE
23+
PS>Get-IcingaWindowsInformation -ClassName MSFT_NetAdapter -NameSpace 'root\StandardCimv2';
24+
.EXAMPLE
25+
PS>Get-IcingaWindowsInformation Win32_LogicalDisk -Filter 'DriveType = 3';
26+
.INPUTS
27+
System.String
28+
.OUTPUTS
29+
System.Boolean
30+
#>
31+
132
function Get-IcingaWindowsInformation()
233
{
334
param (
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<#
2+
.SYNOPSIS
3+
Returns several information about the Wmi namespace and the provided user data to
4+
work with them while adding/testing/removing Wmi permissions
5+
.DESCRIPTION
6+
Returns several information about the Wmi namespace and the provided user data to
7+
work with them while adding/testing/removing Wmi permissions
8+
.PARAMETER User
9+
The user to set permissions for. Can either be a local or domain user
10+
.PARAMETER Namespace
11+
The Wmi namespace to grant permissions for. Required namespaces are listed within each plugin documentation
12+
.INPUTS
13+
System.String
14+
.OUTPUTS
15+
System.Hashtable
16+
#>
17+
18+
function Get-IcingaWmiSecurityData()
19+
{
20+
param (
21+
[string]$User,
22+
[string]$Namespace
23+
);
24+
25+
[hashtable]$WmiArguments = @{
26+
'Name' = 'GetSecurityDescriptor';
27+
'Namespace' = $Namespace;
28+
'Path' = "__systemsecurity=@";
29+
}
30+
31+
$WmiSecurityData = Invoke-WmiMethod @WmiArguments;
32+
33+
if ($WmiSecurityData.ReturnValue -ne 0) {
34+
Write-IcingaConsoleError 'Fetching Wmi security descriptor information failed with error {0}' -Objects $WmiSecurityData.ReturnValue;
35+
return $null;
36+
}
37+
38+
$UserData = Split-IcingaUserDomain -User $User;
39+
$UserSID = Get-IcingaUserSID -User $User;
40+
$WmiAcl = $WmiSecurityData.Descriptor;
41+
42+
$WmiAccount = Get-IcingaWindowsInformation -ClassName Win32_Account -Filter ([string]::Format("Domain='{0}' and Name='{1}'", $UserData.Domain, $UserData.User));
43+
44+
if ($null -eq $WmiAccount) {
45+
Write-IcingaConsoleError 'The specified user could not be found on the system: "{0}\{1}"' -Objects $UserData.Domain, $UserData.User;
46+
return $null;
47+
}
48+
49+
if ([string]::IsNullOrEmpty($UserSID)) {
50+
Write-IcingaConsoleError 'Unable to load the SID for user "{0}"' -Objects $User;
51+
return $null;
52+
}
53+
54+
return @{
55+
'WmiArguments' = $WmiArguments;
56+
'UserData' = $UserData;
57+
'UserSID' = $UserSID;
58+
'WmiAcl' = $WmiAcl;
59+
}
60+
}

lib/wmi/Icinga_WBEM_Security.psm1

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<#
2+
# WMI WBEM_SECURITY_FLAGS
3+
# https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/ne-wbemcli-wbem_security_flags
4+
# https://docs.microsoft.com/en-us/windows/win32/secauthz/standard-access-rights
5+
#>
6+
7+
[hashtable]$SecurityFlags = @{
8+
'WBEM_Enable' = 1;
9+
'WBEM_Method_Execute' = 2;
10+
'WBEM_Full_Write_Rep' = 4;
11+
'WBEM_Partial_Write_Rep' = 8;
12+
'WBEM_Write_Provider' = 0x10;
13+
'WBEM_Remote_Access' = 0x20;
14+
'WBEM_Right_Subscribe' = 0x40;
15+
'WBEM_Right_Publish' = 0x80;
16+
'Read_Control' = 0x20000;
17+
'Write_DAC' = 0x40000;
18+
};
19+
20+
[hashtable]$SecurityDescription = @{
21+
1 = 'Enables the account and grants the user read permissions. This is a default access right for all users and corresponds to the Enable Account permission on the Security tab of the WMI Control. For more information, see Setting Namespace Security with the WMI Control.';
22+
2 = 'Allows the execution of methods. Providers can perform additional access checks. This is a default access right for all users and corresponds to the Execute Methods permission on the Security tab of the WMI Control.';
23+
4 = 'Allows a user account to write to classes in the WMI repository as well as instances. A user cannot write to system classes. Only members of the Administrators group have this permission. WBEM_FULL_WRITE_REP corresponds to the Full Write permission on the Security tab of the WMI Control.';
24+
8 = 'Allows you to write data to instances only, not classes. A user cannot write classes to the WMI repository. Only members of the Administrators group have this right. WBEM_PARTIAL_WRITE_REP corresponds to the Partial Write permission on the Security tab of the WMI Control.';
25+
0x10 = 'Allows writing classes and instances to providers. Note that providers can do additional access checks when impersonating a user. This is a default access right for all users and corresponds to the Provider Write permission on the Security tab of the WMI Control.';
26+
0x20 = 'Allows a user account to remotely perform any operations allowed by the permissions described above. Only members of the Administrators group have this right. WBEM_REMOTE_ACCESS corresponds to the Remote Enable permission on the Security tab of the WMI Control.';
27+
0x40 = 'Specifies that a consumer can subscribe to the events delivered to a sink. Used in IWbemEventSink::SetSinkSecurity.';
28+
0x80 = 'Specifies that the account can publish events to the instance of __EventFilter that defines the event filter for a permanent consumer. Available in wbemcli.h.';
29+
0x20000 = 'The right to read the information in the objects security descriptor, not including the information in the system access control list (SACL).';
30+
0x40000 = 'The right to modify the discretionary access control list (DACL) in the objects security descriptor.';
31+
};
32+
33+
[hashtable]$SecurityNames = @{
34+
'Enable' = 'WBEM_Enable';
35+
'MethodExecute' = 'WBEM_Method_Execute';
36+
'FullWrite' = 'WBEM_Full_Write_Rep';
37+
'PartialWrite' = 'WBEM_Partial_Write_Rep';
38+
'ProviderWrite' = 'WBEM_Write_Provider';
39+
'RemoteAccess' = 'WBEM_Remote_Access';
40+
'Subscribe' = 'WBEM_Right_Subscribe';
41+
'Publish' = 'WBEM_Right_Publish';
42+
'ReadSecurity' = 'Read_Control';
43+
'WriteSecurity' = 'Write_DAC';
44+
};
45+
46+
[hashtable]$AceFlags = @{
47+
'Access_Allowed' = 0x0;
48+
'Access_Denied' = 0x1;
49+
'Container_Inherit' = 0x2;
50+
}
51+
52+
[hashtable]$IcingaWBEM = @{
53+
SecurityFlags = $SecurityFlags;
54+
SecurityDescription = $SecurityDescription
55+
SecurityNames = $SecurityNames;
56+
AceFlags = $AceFlags;
57+
}
58+
59+
Export-ModuleMember -Variable @( 'IcingaWBEM' );

0 commit comments

Comments
 (0)