Skip to content

Commit 3886dc0

Browse files
committed
Fixes security issue by restricting access to core folders of Icinga for Windows for generic users
1 parent 87c9043 commit 3886dc0

7 files changed

Lines changed: 252 additions & 33 deletions

File tree

doc/100-General/10-Changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ documentation before upgrading to a new release.
77

88
Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga-powershell-framework/milestones?state=closed).
99

10+
## 1.13.4 (2026-01-29)
11+
12+
### Security
13+
14+
* [#850](https://github.com/Icinga/icinga-powershell-framework/pull/850) Fixes security issue, allowing to access the private key file of the Icinga Agent/Icinga for Windows certificate file [GHSA](https://github.com/Icinga/icinga-powershell-framework/security/advisories/GHSA-88h5-rrm6-5973) [CVE-2026-24414](https://www.cve.org/CVERecord?id=CVE-2026-24414)
15+
1016
## 1.13.3 (2025-05-08)
1117

1218
[Issues and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/39)

jobs/RenewCertificate.ps1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ while ($TRUE) {
3333
continue;
3434
}
3535

36+
# Let make sure the permissions are updated for the entire Icinga installation
37+
# on a regular basis to ensure system integrity, in case users or other processes
38+
# modify the permissions incorrectly
39+
Set-IcingaUserPermissions;
40+
3641
break;
3742
}
3843

lib/core/framework/Invoke-IcingaForWindowsMigration.psm1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,17 @@ function Invoke-IcingaForWindowsMigration()
174174

175175
Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.13.3');
176176
}
177+
178+
if (Test-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.13.4')) {
179+
Write-IcingaConsoleNotice 'Applying pending migrations required for Icinga for Windows v1.13.4';
180+
181+
# Fixes potential permission issues with the Icinga Service User and the SeServiceLogonRight privilege
182+
$ServiceUser = Get-IcingaServiceUser;
183+
$ServiceUserSID = Get-IcingaUserSID $ServiceUser;
184+
185+
Update-IcingaWindowsUserPermission -SID $ServiceUserSID;
186+
Set-IcingaUserPermissions -IcingaUser $ServiceUser;
187+
188+
Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.13.4');
189+
}
177190
}
Lines changed: 179 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,196 @@
1+
<#
2+
.SYNOPSIS
3+
Configure strict ACLs for a directory so only Administrators, Domain Admins (optional)
4+
and the specified Icinga service user(s) have FullControl.
5+
6+
.DESCRIPTION
7+
This function validates the provided accounts, disables inheritance on the target
8+
directory, clears existing explicit ACL entries, sets the directory owner and
9+
grants FullControl recursively to the local Administrators group, an optional
10+
Domain Admins group and one or more Icinga service user accounts.
11+
12+
.PARAMETER Directory
13+
The path to the target directory to update ACLs for. Must be a directory path.
14+
15+
.PARAMETER Owner
16+
The account to set as owner of the directory. Default is 'Administrators'.
17+
18+
.PARAMETER IcingaUser
19+
One or more accounts (string or string[]) which should receive FullControl on
20+
the directory. Default is the value returned by `Get-IcingaServiceUser`.
21+
22+
.PARAMETER DomainName
23+
Optional domain name. When provided the function will also grant FullControl to
24+
'<DomainName>\Domain Admins'.
25+
26+
.OUTPUTS
27+
None. This function writes progress and errors to the Icinga console helpers.
28+
29+
.NOTES
30+
- Requires administrative privileges to set ownership and modify ACLs on system
31+
directories.
32+
- Intended for use on Windows hosts only.
33+
#>
134
function Set-IcingaAcl()
235
{
3-
param(
4-
[string]$Directory,
5-
[string]$IcingaUser = (Get-IcingaServiceUser),
6-
[switch]$Remove = $FALSE
36+
param (
37+
[string]$Directory = $null,
38+
[string]$Owner = 'NT AUTHORITY\SYSTEM',
39+
[string[]]$IcingaUser = (Get-IcingaServiceUser),
40+
[string]$DomainName = ($env:USERDOMAIN).ToLower()
741
);
842

9-
if (-Not (Test-Path $Directory)) {
10-
Write-IcingaConsoleWarning 'Unable to set ACL for directory "{0}". Directory does not exist' -Objects $Directory;
43+
# First check if the directory exists
44+
if (-not (Test-Path -Path $Directory -PathType Container)) {
45+
Write-IcingaConsoleWarning -Message 'The folder does not exist: {0}' -Objects $Directory;
1146
return;
1247
}
1348

14-
if ($IcingaUser.ToLower() -eq 'nt authority\system' -Or $IcingaUser.ToLower() -like '*localsystem') {
49+
# Create the owner account object and validate
50+
try {
51+
$ownerAccount = New-Object System.Security.Principal.NTAccount($Owner);
52+
$ownerAccount.Translate([System.Security.Principal.SecurityIdentifier]) | Out-Null;
53+
} catch {
54+
Write-IcingaConsoleError -Message 'The owner account does not exist or is invalid: {0}' -Objects $Owner;
1555
return;
1656
}
1757

18-
$DirectoryAcl = (Get-Item -Path $Directory).GetAccessControl('Access');
19-
$DirectoryAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
20-
$IcingaUser,
21-
'Modify',
22-
'ContainerInherit,ObjectInherit',
23-
'None',
24-
'Allow'
25-
);
58+
# Create and validate IcingaUser accounts
59+
foreach ($user in $IcingaUser) {
60+
try {
61+
$userAccount = New-Object System.Security.Principal.NTAccount($user);
62+
$userAccount.Translate([System.Security.Principal.SecurityIdentifier]) | Out-Null;
63+
} catch {
64+
Write-IcingaConsoleError -Message 'The user account does not exist or is invalid: {0}' -Objects $user;
65+
return;
66+
}
67+
}
2668

27-
if ($Remove -eq $FALSE) {
28-
$DirectoryAcl.SetAccessRule($DirectoryAccessRule);
29-
} else {
30-
foreach ($entry in $DirectoryAcl.Access) {
31-
if (([string]($entry.IdentityReference)).ToLower() -like [string]::Format('*\{0}', $IcingaUser.ToLower())) {
32-
$DirectoryAcl.RemoveAccessRuleSpecific($entry);
33-
}
69+
# Validate if the local Administrators group exists (shouldn't happen anyway)
70+
try {
71+
$adminGroup = New-Object System.Security.Principal.NTAccount('Administrators');
72+
$adminGroup.Translate([System.Security.Principal.SecurityIdentifier]) | Out-Null;
73+
} catch {
74+
Write-IcingaConsoleError -Message 'The local Administrators group does not exist or is invalid' -Objects $null;
75+
return;
76+
}
77+
78+
[bool]$AddDomainAdmins = $TRUE;
79+
80+
# Validate if the Domain Admins group exists (if DomainName is provided)
81+
if (-not [string]::IsNullOrEmpty($DomainName) ) {
82+
try {
83+
$domainAdminGroup = New-Object System.Security.Principal.NTAccount(([string]::Format('{0}\Domain Admins', $DomainName)));
84+
$domainAdminGroup.Translate([System.Security.Principal.SecurityIdentifier]) | Out-Null;
85+
} catch {
86+
# Continue in this case, just warn the user
87+
Write-IcingaConsoleWarning -Message 'The Domain Admins group does not exist or is invalid: {0}' -Objects ([string]::Format('{0}\Domain Admins', $DomainName));
88+
$AddDomainAdmins = $FALSE;
3489
}
3590
}
3691

37-
Set-Acl -Path $Directory -AclObject $DirectoryAcl;
92+
try {
93+
# Get the ACL for the directory
94+
$acl = Get-Acl -Path $Directory;
95+
96+
# Now disable inheritance for the parent folder
97+
$acl.SetAccessRuleProtection($true, $false) | Out-Null;
98+
99+
# Update the owner of the folder to "Administrators" first, to ensure we don't
100+
# run into any exceptions
101+
$acl.SetOwner((New-Object System.Security.Principal.NTAccount('Administrators'))) | Out-Null;
102+
103+
Write-IcingaConsoleNotice -Message 'Disabled inheritance for directory {0}' -Objects $Directory;
104+
105+
# Clear all existing ACL entries to ensure we start fresh
106+
$acl.Access | ForEach-Object {
107+
$acl.RemoveAccessRule($_) | Out-Null;
108+
};
109+
110+
Write-IcingaConsoleNotice -Message 'Cleared existing ACL entries for directory {0}' -Objects $Directory;
111+
112+
# Add the permission for each defined user with Full Control
113+
# Only add Icinga user permissions if we are not removing them
114+
foreach ($user in $IcingaUser) {
115+
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
116+
$user,
117+
'FullControl',
118+
'ContainerInherit, ObjectInherit',
119+
'None',
120+
'Allow'
121+
);
122+
$acl.AddAccessRule($rule) | Out-Null;
123+
}
124+
125+
# Add local Administrators group (Full Control)
126+
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
127+
'Administrators',
128+
'FullControl',
129+
'ContainerInherit, ObjectInherit',
130+
'None',
131+
'Allow'
132+
);
133+
134+
$acl.AddAccessRule($adminRule) | Out-Null;
135+
136+
# We need to ensure we add Domain Admins, as most likely those will require to have access as well
137+
# and allow them to configure Icinga for Windows
138+
if ($AddDomainAdmins) {
139+
$domainAdminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
140+
([string]::Format('{0}\Domain Admins', $DomainName)),
141+
'FullControl',
142+
'ContainerInherit, ObjectInherit',
143+
'None',
144+
'Allow'
145+
);
146+
$acl.AddAccessRule($domainAdminRule) | Out-Null;
147+
}
148+
149+
Write-IcingaConsoleNotice -Message 'Configured new ACL entries for directory {0}' -Objects $Directory;
150+
151+
# Update the ACL on the directory
152+
Set-Acl -Path $Directory -AclObject $acl | Out-Null;
153+
154+
# Let's now enable inheritance for all subfolders and files, ensuring they inherit the correct permissions
155+
# from the parent folder and we only require to configure permissions there
156+
Get-ChildItem -Path $Directory -Recurse -Force | ForEach-Object {
157+
try {
158+
$childAcl = Get-Acl -Path $_.FullName;
159+
$childAcl.SetAccessRuleProtection($false, $true) | Out-Null;
160+
# As our parent or current Acl might be owned by SYSTEM,
161+
# we need to set the owner to Administrators here as well to fix exceptions
162+
# for SYSTEM user not being allowed to own this file
163+
$childAcl.SetOwner((New-Object System.Security.Principal.NTAccount('Administrators'))) | Out-Null;
164+
165+
Set-Acl -Path $_.FullName -AclObject $childAcl | Out-Null;
166+
} catch {
167+
Write-IcingaConsoleWarning -Message 'Failed to enable inheritance for directory {0}: {1}' -Objects $_.FullName, $_.Exception.Message;
168+
}
169+
}
170+
171+
# Ensure we set the owner of the directory and all sub-items correctly
172+
# Set-Acl cannot do this for the SYSTEM user and ProgramData folders
173+
# properly, so we need to use icacls for this task
174+
$Result = & icacls $Directory /setowner $Owner /T /C | Out-Null;
175+
176+
if ($LASTEXITCODE -ne 0) {
177+
Write-IcingaConsoleError -Message 'Failed to set owner "{0}" for directory {1} and its sub-items using icacls. Output: {2}' -Objects $Owner, $Directory, $Result;
178+
}
179+
180+
$StringBuilder = New-Object System.Text.StringBuilder;
181+
$StringBuilder.Append('Permissions for directory "').Append($Directory).Append('" successfully configured for owner "').Append($Owner).Append('"') | Out-Null;
182+
$StringBuilder.Append(' and full access users (').Append(($IcingaUser -join ', ')).Append(')') | Out-Null;
183+
184+
$StringBuilder.Append(' and groups (Administrators') | Out-Null;
185+
186+
if ($AddDomainAdmins) {
187+
$StringBuilder.Append(', ').Append(([string]::Format('{0}\Domain Admins)', $DomainName))) | Out-Null;
188+
} else {
189+
$StringBuilder.Append(')') | Out-Null;
190+
};
38191

39-
if ($Remove -eq $FALSE) {
40-
Test-IcingaAcl -Directory $Directory -WriteOutput | Out-Null;
192+
Write-IcingaConsoleNotice -Message $StringBuilder.ToString();
193+
} catch {
194+
Write-IcingaConsoleError -Message 'Failed to Update ACL for directory {0}: {1}' -Objects $Directory, $_.Exception.Message;
41195
}
42196
}
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1+
<#
2+
.SYNOPSIS
3+
Apply `Set-IcingaAcl` to the common Icinga for Windows directories.
4+
5+
.DESCRIPTION
6+
Convenience wrapper that calls `Set-IcingaAcl` for a pre-defined set of Icinga
7+
directories (configuration, var, cache and PowerShell config directories).
8+
Use this to consistently apply the same ACL rules to the standard locations.
9+
10+
.PARAMETER IcingaUser
11+
The account or accounts to grant FullControl. Defaults to the value returned
12+
by `Get-IcingaServiceUser`.
13+
14+
.OUTPUTS
15+
None. Operates by calling `Set-IcingaAcl` which emits its own console output.
16+
17+
.NOTES
18+
- Requires administrative privileges to change ACLs on system folders.
19+
#>
120
function Set-IcingaUserPermissions()
221
{
322
param (
4-
[string]$IcingaUser = (Get-IcingaServiceUser),
5-
[switch]$Remove = $FALSE
23+
[string]$IcingaUser = (Get-IcingaServiceUser)
624
);
725

8-
Set-IcingaAcl -Directory "$Env:ProgramData\icinga2\etc" -IcingaUser $IcingaUser -Remove:$Remove;
9-
Set-IcingaAcl -Directory "$Env:ProgramData\icinga2\var" -IcingaUser $IcingaUser -Remove:$Remove;
10-
Set-IcingaAcl -Directory (Get-IcingaCacheDir) -IcingaUser $IcingaUser -Remove:$Remove;
11-
Set-IcingaAcl -Directory (Get-IcingaPowerShellConfigDir) -IcingaUser $IcingaUser -Remove:$Remove;
12-
Set-IcingaAcl -Directory (Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath 'certificate') -IcingaUser $IcingaUser -Remove:$Remove;
26+
Set-IcingaAcl -Directory "$Env:ProgramData\icinga2\etc" -IcingaUser $IcingaUser;
27+
Set-IcingaAcl -Directory "$Env:ProgramData\icinga2\var" -IcingaUser $IcingaUser;
28+
Set-IcingaAcl -Directory (Get-IcingaCacheDir) -IcingaUser $IcingaUser;
29+
Set-IcingaAcl -Directory (Get-IcingaPowerShellConfigDir) -IcingaUser $IcingaUser;
30+
Set-IcingaAcl -Directory (Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath 'certificate') -IcingaUser $IcingaUser;
1331
}

lib/core/icingaagent/tests/Test-IcingaForWindows.psm1

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
<#
2+
.SYNOPSIS
3+
Run a set of environment checks for Icinga for Windows and optionally fix issues.
4+
5+
.DESCRIPTION
6+
Performs multiple validation checks for the local Icinga for Windows installation,
7+
including presence of services, service users, file permissions, certificates,
8+
REST API availability and other environment items. When the `-ResolveProblems`
9+
switch is used the function will attempt to repair certain detected issues.
10+
11+
.PARAMETER ResolveProblems
12+
When provided, the function will attempt to fix detected problems where the
13+
code includes a repair implementation (for example repairing services, updating
14+
ACLs, renewing certificates). Use with caution as fixes may change system state.
15+
16+
.OUTPUTS
17+
None. Results are conveyed using the framework's `Write-IcingaTestOutput` helpers.
18+
19+
.NOTES
20+
- Some repair actions require administrative privileges.
21+
- Designed to run on Windows systems where Icinga for Windows is installed.
22+
#>
123
function Test-IcingaForWindows()
224
{
325
param (

lib/core/windows/Uninstall-IcingaServiceUser.psm1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ function Uninstall-IcingaServiceUser()
1919
Set-IcingaServiceUser -User 'NT Authority\NetworkService' -Service 'icinga2' | Out-Null;
2020
Set-IcingaServiceUser -User 'NT Authority\NetworkService' -Service 'icingapowershell' | Out-Null;
2121

22-
Set-IcingaUserPermissions -IcingaUser $IcingaUser -Remove;
22+
Set-IcingaUserPermissions -IcingaUser 'NT Authority\NetworkService';
23+
Update-IcingaWindowsUserPermission -SID $ServiceUserSID -Remove;
2324

2425
$UserConfig = Remove-IcingaWindowsUser -IcingaUser $IcingaUser;
2526

0 commit comments

Comments
 (0)