Skip to content

Commit

Permalink
Merge pull request #690 from Icinga:features/adds_api_certificate_ren…
Browse files Browse the repository at this point in the history
…ewal

Feature: Adds renewal handling for Icinga for Windows certificate

Adds automatic renewal of the `icingaforwindows.pfx` certificate for the REST-Api daemon in case the certificate is not yet present, valid or changed during the runtime of the daemon while also making the `icingaforwindows.pfx` mandatory for all installations, regardless of JEA being used or not
  • Loading branch information
LordHepipud committed Mar 13, 2024
2 parents c7eeab8 + 03e60d4 commit 6a4b067
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 28 deletions.
1 change: 1 addition & 0 deletions doc/100-General/10-Changelog.md
Expand Up @@ -23,6 +23,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
* [#631](https://github.com/Icinga/icinga-powershell-framework/pull/631) Deduplicates `-C try { Use-Icinga ...` boilerplate by adding it to the `PowerShell Base` template and removing it from every single command
* [#679](https://github.com/Icinga/icinga-powershell-framework/pull/679) Adds a new data provider for fetching process information of Windows systems, while sorting all objects based on a process name and their process id
* [#688](https://github.com/Icinga/icinga-powershell-framework/pull/688) Adds new handling to add scheduled tasks in Windows for interacting with Icinga for Windows core functionality as well as an auto renewal task for the Icinga for Windows certificate generation
* [#690](https://github.com/Icinga/icinga-powershell-framework/pull/690) Adds automatic renewal of the `icingaforwindows.pfx` certificate for the REST-Api daemon in case the certificate is not yet present, valid or changed during the runtime of the daemon while also making the `icingaforwindows.pfx` mandatory for all installations, regardless of JEA being used or not

## 1.11.2 (tbd)

Expand Down
2 changes: 2 additions & 0 deletions lib/core/framework/Invoke-IcingaForWindowsMigration.psm1
Expand Up @@ -95,6 +95,8 @@ function Invoke-IcingaForWindowsMigration()

# Add a new scheduled task to automatically renew the Icinga for Windows certificate
Register-IcingaWindowsScheduledTaskRenewCertificate -Force;
# Start the task to ensure the certificate is generated
Start-IcingaWindowsScheduledTaskRenewCertificate;

Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.12.0');
}
Expand Down
8 changes: 3 additions & 5 deletions lib/core/installer/Start-IcingaForWindowsInstallation.psm1
Expand Up @@ -312,11 +312,9 @@ function Start-IcingaForWindowsInstallation()
};
}

# Install Icinga for Windows certificate if both, JEA and REST is installed
if ($InstallJEA -And $InstallRESTApi) {
Install-IcingaForWindowsCertificate;
Restart-IcingaWindowsService;
}
# Always install the Icinga for Windows certificate
Install-IcingaForWindowsCertificate;
Restart-IcingaWindowsService;

# Update configuration and clear swap
$ConfigSwap = Get-IcingaPowerShellConfig -Path 'Framework.Config.Swap';
Expand Down
Expand Up @@ -7,10 +7,8 @@ function Invoke-IcingaForWindowsManagementConsoleToggleFrameworkApiChecks()
Register-IcingaBackgroundDaemon -Command 'Start-IcingaWindowsRESTApi';
Add-IcingaRESTApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'apichecks';
}
if ([string]::IsNullOrEmpty((Get-IcingaJEAContext)) -eq $FALSE) {
Install-IcingaForWindowsCertificate;
}

Install-IcingaForWindowsCertificate;
Enable-IcingaFrameworkApiChecks;
}

Expand Down
5 changes: 1 addition & 4 deletions lib/core/jea/Install-IcingaJeaProfile.psm1
Expand Up @@ -26,10 +26,7 @@ function Install-IcingaJEAProfile()
Write-IcingaJEAProfile -RebuildFramework:$RebuildFramework -AllowScriptBlocks:$AllowScriptBlocks;
Write-IcingaConsoleNotice 'Registering Icinga for Windows JEA profile'
Register-IcingaJEAProfile -IcingaUser $IcingaUser -TestEnv:$TestEnv -ConstrainedLanguage:$ConstrainedLanguage;

if ((Get-IcingaBackgroundDaemons).ContainsKey('Start-IcingaWindowsRESTApi')) {
Install-IcingaForWindowsCertificate;
}
Install-IcingaForWindowsCertificate;
}

Set-Alias -Name 'Update-IcingaJEAProfile' -Value 'Install-IcingaJEAProfile';
4 changes: 2 additions & 2 deletions lib/core/wintasks/daemon/Register-TaskRenewCertificate.psm1
Expand Up @@ -20,7 +20,7 @@ function Register-IcingaWindowsScheduledTaskRenewCertificate()
$TaskPrincipal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-544' -RunLevel 'Highest';
$TaskSettings = New-ScheduledTaskSettingsSet -DontStopIfGoingOnBatteries -AllowStartIfOnBatteries -StartWhenAvailable;

Register-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Force -Principal $TaskPrincipal -Action $TaskAction -Trigger $TaskTrigger -Settings $TaskSettings;
Register-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Force -Principal $TaskPrincipal -Action $TaskAction -Trigger $TaskTrigger -Settings $TaskSettings | Out-Null;

Write-IcingaConsoleWarning -Message 'The task "{0}" has been successfully registered at location "{1}".' -Objects $TaskName, $TaskPath;
Write-IcingaConsoleNotice -Message 'The task "{0}" has been successfully registered at location "{1}".' -Objects $TaskName, $TaskPath;
}
1 change: 0 additions & 1 deletion lib/daemon/Start-IcingaPowerShellDaemon.psm1
Expand Up @@ -39,7 +39,6 @@ function Start-IcingaForWindowsDaemon()
Add-IcingaThreadPool -Name 'MainPool' -MaxInstances 20;
$Global:Icinga.Public.Add('SSLCertificate', $Certificate);


New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start;
} else {
Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service inside JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile;
Expand Down
23 changes: 11 additions & 12 deletions lib/daemons/RestAPI/daemon/New-IcingaForWindowsRESTApi.psm1
Expand Up @@ -54,21 +54,20 @@ function New-IcingaForWindowsRESTApi()

Write-IcingaDebugMessage -Message ($Global:Icinga.Public.Daemons.RESTApi.RegisteredEndpoints | Out-String);

if ($Global:Icinga.Protected.JEAContext) {
if ($Global:Icinga.Public.ContainsKey('SSLCertificate') -eq $FALSE -Or $null -eq $Global:Icinga.Public.SSLCertificate) {
Write-IcingaEventMessage -EventId 2001 -Namespace 'RESTApi';
return;
while ($TRUE) {
if ($null -eq $Global:Icinga.Public.SSLCertificate) {
$Global:Icinga.Public.SSLCertificate = (Get-IcingaForWindowsCertificate);
} else {
break;
}

$Certificate = $Global:Icinga.Public.SSLCertificate;
} else {
$Certificate = Get-IcingaSSLCertForSocket -CertFile $CertFile -CertThumbprint $CertThumbprint;
# Wait 5 minutes and try again
Write-IcingaEventMessage -EventId 2002 -Namespace 'RESTApi';
Start-Sleep -Seconds (60 * 5);
}

if ($null -eq $Certificate) {
Write-IcingaEventMessage -EventId 2000 -Namespace 'RESTApi';
return;
}
# Create a background thread to renew the certificate on a regular basis
Start-IcingaForWindowsCertificateThreadTask;

$Socket = New-IcingaTCPSocket -Address $Address -Port $Port -Start;

Expand All @@ -82,7 +81,7 @@ function New-IcingaForWindowsRESTApi()

$Connection = Open-IcingaTCPClientConnection `
-Client (New-IcingaTCPClient -Socket $Socket) `
-Certificate $Certificate;
-Certificate $Global:Icinga.Public.SSLCertificate;

if ($Connection.Client -eq $null -Or $Connection.Stream -eq $null) {
Close-IcingaTCPConnection -Connection $Connection;
Expand Down
Expand Up @@ -11,9 +11,27 @@ function Register-IcingaEventLogMessagesRESTApi()
2001 = @{
'EntryType' = 'Error';
'Message' = 'Failed to start REST-Api daemon in JEA context';
'Details' = 'Icinga for Windows is being used inside a JEA context as service with the REST-Api daemon. To establish a secure TLS socket, it is required to create certificates in advance for the socket to bind on with "Install-IcingaForWindowsCertificate". The REST-Api daemon will now exit.';
'Details' = 'Icinga for Windows is being used inside a JEA context as service with the REST-Api daemon. To establish a secure TLS socket, it is required to create certificates in advance for the socket to bind on with "Start-IcingaWindowsScheduledTaskRenewCertificate". The REST-Api daemon will now exit.';
'EventId' = 2001;
};
2002 = @{
'EntryType' = 'Warning';
'Message' = 'Icinga for Windows certificate not ready';
'Details' = 'The Icinga for Windows REST-Api was not able to fetch the icingaforwindows.pfx certificate file. You can manually enforce the certificate creation by using the command "Start-IcingaWindowsScheduledTaskRenewCertificate". Once successful, this message should disappear and the REST-Api start. If the error persist, ensure your Icinga Agent certificate is configured properly and signed by your Icinga CA. This check is queued every 5 minutes and should vanish once everything works fine.';
'EventId' = 2002;
};
2003 = @{
'EntryType' = 'Warning';
'Message' = 'Icinga for Windows certificate was not found';
'Details' = 'The Icinga for Windows "icingaforwindows.pfx" file was not found on the system while the REST-Api is running. Please ensure the certificate is created shortly, as the daemon will no longer work once it will be restarted or the certificate is due for renewal. Please run "Start-IcingaWindowsScheduledTaskRenewCertificate" to re-create the certificate on your machine.'
'EventId' = 2003;
};
2004 = @{
'EntryType' = 'Information';
'Message' = 'Icinga for Windows certificate was renewed';
'Details' = 'The Icinga for Windows certificate has been modified and was updated inside the Icinga for Windows REST-Api daemon.'
'EventId' = 2004;
};
2050 = @{
'EntryType' = 'Error';
'Message' = 'Failed to parse received REST-Api call';
Expand Down
@@ -0,0 +1,23 @@
function New-IcingaForWindowsCertificateThreadTaskInstance()
{
$IcingaHostname = Get-IcingaHostname -ReadConstants;

while ($TRUE) {
# Check every 10 minutes if our certificate is present and update it in case it is
# missing or updates have happened
$NewIcingaForWindowsCertificate = Get-IcingaForWindowsCertificate;

if ($null -ne $NewIcingaForWindowsCertificate) {
if ($NewIcingaForWindowsCertificate.Issuer.ToLower() -eq ([string]::Format('cn={0}', $IcingaHostname).ToLower())) {
Write-IcingaEventMessage -EventId 1506 -Namespace 'Framework';
} else {
if ($Global:Icinga.Public.SSLCertificate.GetCertHashString() -ne $NewIcingaForWindowsCertificate.GetCertHashString()) {
$Global:Icinga.Public.SSLCertificate = $NewIcingaForWindowsCertificate;
Write-IcingaEventMessage -EventId 2004 -Namespace 'RESTApi';
}
}
}

Start-Sleep -Seconds (60 * 10);
}
}
@@ -0,0 +1,8 @@
function Start-IcingaForWindowsCertificateThreadTask()
{
New-IcingaThreadInstance `
-Name 'CertificateRenewThread' `
-ThreadPool (New-IcingaThreadPool -MaxInstances 1) `
-Command 'New-IcingaForWindowsCertificateThreadTaskInstance' `
-Start;
}

0 comments on commit 6a4b067

Please sign in to comment.