Merge pull request #454 from Icinga:fix/jea_background_daemon_handling

Fix: Background daemon not working in JEA context

The current implementation of the Icinga for Windows background daemon was not working entirely by using JEa profiles.

We fixed the behavior by resolving a problem within our JEA profile catalog builder as well as other factors causing issues. All background daemons will now work properly inside the JEA context.
This commit is contained in:
Lord Hepipud 2022-01-28 21:07:47 +01:00 committed by GitHub
commit ca1eab8b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 149 additions and 35 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ cache/*
.vscode/ .vscode/
.vs/ .vs/
*.log *.log
*.pfx
# JEA # JEA
RoleCapabilities/IcingaForWindows.psrc RoleCapabilities/IcingaForWindows.psrc

View file

@ -35,6 +35,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
* [#449](https://github.com/Icinga/icinga-powershell-framework/pull/449) Fixes unhandled exception while importing modules during `Install-IcingaComponent` process, because of possible missing dependencies * [#449](https://github.com/Icinga/icinga-powershell-framework/pull/449) Fixes unhandled exception while importing modules during `Install-IcingaComponent` process, because of possible missing dependencies
* [#451](https://github.com/Icinga/icinga-powershell-framework/pull/451) Fixes PowerShell being unable to enter JEA context if only the Framework is installed and removes the `|` from plugin output, in case a JEA error is thrown that check commands are not present * [#451](https://github.com/Icinga/icinga-powershell-framework/pull/451) Fixes PowerShell being unable to enter JEA context if only the Framework is installed and removes the `|` from plugin output, in case a JEA error is thrown that check commands are not present
* [#452](https://github.com/Icinga/icinga-powershell-framework/pull/452) Fixes unhandled `true` output on the console while running the installer * [#452](https://github.com/Icinga/icinga-powershell-framework/pull/452) Fixes unhandled `true` output on the console while running the installer
* [#454](https://github.com/Icinga/icinga-powershell-framework/pull/454) Fixes JEA catalog compiler and background daemon execution in JEA context
### Enhancements ### Enhancements

View file

@ -172,8 +172,6 @@ function New-IcingaForWindowsComponent()
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # This is your main daemon function. Add your code inside the WHILE() loop which is executed once the daemon is loaded.'; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # This is your main daemon function. Add your code inside the WHILE() loop which is executed once the daemon is loaded.';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # Also check the developer guide for further details: https://icinga.com/docs/icinga-for-windows/latest/doc/900-Developer-Guide/10-Custom-Daemons/'; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # Also check the developer guide for further details: https://icinga.com/docs/icinga-for-windows/latest/doc/900-Developer-Guide/10-Custom-Daemons/';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ''; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value '';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' Use-Icinga -LibOnly -Daemon;';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value '';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' while ($TRUE) {'; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' while ($TRUE) {';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # Add your daemon code within this loop'; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # Add your daemon code within this loop';
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' Start-Sleep -Seconds 1;'; Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' Start-Sleep -Seconds 1;';

View file

@ -7,40 +7,54 @@ function Get-IcingaCommandDependency()
[string]$CmdType = '' [string]$CmdType = ''
); );
# Function, Cmdlet, Alias, Modules, Application
if ([string]::IsNullOrEmpty($CmdType)) { if ([string]::IsNullOrEmpty($CmdType)) {
return $CompiledList; return $CompiledList;
} }
# Create the list container for our object type if not existing
# => Function, Cmdlet, Alias, Modules, Application
if ($CompiledList.ContainsKey($CmdType) -eq $FALSE) { if ($CompiledList.ContainsKey($CmdType) -eq $FALSE) {
$CompiledList.Add($CmdType, @{ }); $CompiledList.Add($CmdType, @{ });
} }
# e.g. Invoke-IcingaCheckCPU
if ($CompiledList[$CmdType].ContainsKey($CmdName)) { if ($CompiledList[$CmdType].ContainsKey($CmdName)) {
$CompiledList[$CmdType][$CmdName] += 1; $CompiledList[$CmdType][$CmdName] += 1;
return $CompiledList; return $CompiledList;
} }
# Add the command this function is called with
$CompiledList[$CmdType].Add($CmdName, 0); $CompiledList[$CmdType].Add($CmdName, 0);
# The command is not known in our Framework dependency list -> could be a native Windows command
if ((Test-PSCustomObjectMember -PSObject $DependencyList -Name $CmdName) -eq $FALSE) { if ((Test-PSCustomObjectMember -PSObject $DependencyList -Name $CmdName) -eq $FALSE) {
return $CompiledList; return $CompiledList;
} }
# Loop our entire dependency list for every single command
foreach ($CmdList in $DependencyList.$CmdName.PSObject.Properties.Name) { foreach ($CmdList in $DependencyList.$CmdName.PSObject.Properties.Name) {
# $Cmd => The list of child commands
# $CmdList => Function, Cmdlet, Alias, Modules, Application
$Cmd = $DependencyList.$CmdName.$CmdList; $Cmd = $DependencyList.$CmdName.$CmdList;
# Create the list container for our object type if not existing
# => Function, Cmdlet, Alias, Modules, Application
if ($CompiledList.ContainsKey($CmdList) -eq $FALSE) { if ($CompiledList.ContainsKey($CmdList) -eq $FALSE) {
$CompiledList.Add($CmdList, @{ }); $CompiledList.Add($CmdList, @{ });
} }
# Loop all commands within our child list for this command
foreach ($entry in $Cmd.PSObject.Properties.Name) { foreach ($entry in $Cmd.PSObject.Properties.Name) {
if ($CompiledList[$CmdList].ContainsKey($entry) -eq $FALSE) {
$CompiledList[$CmdList].Add($entry, 0);
# $entry => The command name e.g. Write-IcingaConsolePlain
if ($CompiledList[$CmdList].ContainsKey($entry) -eq $FALSE) {
$CompiledList = Get-IcingaCommandDependency ` $CompiledList = Get-IcingaCommandDependency `
-DependencyList $DependencyList ` -DependencyList $DependencyList `
-CompiledList $CompiledList ` -CompiledList $CompiledList `
-CmdName $entry; -CmdName $entry `
-CmdType $CmdList;
} else { } else {
$CompiledList[$CmdList][$entry] += 1; $CompiledList[$CmdList][$entry] += 1;
} }

View file

@ -147,11 +147,17 @@ function Get-IcingaJEAConfiguration()
-CmdType 'Function'; -CmdType 'Function';
# We need to add this function for our background daemon we start with 'Start-IcingaForWindowsDaemon', # We need to add this function for our background daemon we start with 'Start-IcingaForWindowsDaemon',
# as this function is called outside the JEA context # as these functions are called outside the JEA context
$UsedCmdlets = Get-IcingaCommandDependency ` $UsedCmdlets = Get-IcingaCommandDependency `
-DependencyList $DependencyList ` -DependencyList $DependencyList `
-CompiledList $UsedCmdlets ` -CompiledList $UsedCmdlets `
-CmdName 'Add-IcingaForWindowsDaemon' ` -CmdName 'Start-IcingaPowerShellDaemon' `
-CmdType 'Function';
$UsedCmdlets = Get-IcingaCommandDependency `
-DependencyList $DependencyList `
-CompiledList $UsedCmdlets `
-CmdName 'Start-IcingaForWindowsDaemon' `
-CmdType 'Function'; -CmdType 'Function';
# Fixes error if only the Icinga PowerShell Framework is installed, which then causes JEA to fail entirely because of this missing Cmdlet # Fixes error if only the Icinga PowerShell Framework is installed, which then causes JEA to fail entirely because of this missing Cmdlet

View file

@ -22,6 +22,10 @@ function Read-IcingaPowerShellModuleFile()
[hashtable]$CmdCache = @{ }; [hashtable]$CmdCache = @{ };
[hashtable]$FncCache = @{ }; [hashtable]$FncCache = @{ };
[int]$Index = 0; [int]$Index = 0;
[bool]$ThreadCommand = $FALSE;
[bool]$ThreadFetchNext = $FALSE;
[bool]$ShellCommand = $FALSE;
[bool]$ShellGroupStart = $FALSE;
foreach ($entry in $PSParser) { foreach ($entry in $PSParser) {
if ($entry.Type -eq 'Comment') { if ($entry.Type -eq 'Comment') {
@ -31,6 +35,12 @@ function Read-IcingaPowerShellModuleFile()
$CommandList += [string]$entry.Content; $CommandList += [string]$entry.Content;
$CmdCache.Add($entry.Content, 0); $CmdCache.Add($entry.Content, 0);
} }
# We need to include commands we call with New-IcingaThreadInstance e.g.
# => New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start;
if ($entry.Content.ToLower() -eq 'new-icingathreadinstance') {
$ThreadCommand = $TRUE;
}
} elseif ($entry.Type -eq 'CommandArgument') { } elseif ($entry.Type -eq 'CommandArgument') {
if ($PSParser[$index - 1].Type -eq 'Keyword' -And $PSParser[$index - 1].Content.ToLower() -eq 'function') { if ($PSParser[$index - 1].Type -eq 'Keyword' -And $PSParser[$index - 1].Content.ToLower() -eq 'function') {
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) { if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
@ -38,6 +48,55 @@ function Read-IcingaPowerShellModuleFile()
$FncCache.Add($entry.Content, 0); $FncCache.Add($entry.Content, 0);
} }
} }
} elseif ($entry.Type -eq 'Member' -And $entry.Content.ToLower() -eq 'addcommand') {
# In case we have objects that use .AddCommand() we should add these to our function list e.g.
# => [void]$Shell.AddCommand('Set-IcingaEnvironmentGlobal');
$ShellCommand = $TRUE;
}
# If we reached -Command for New-IcingaThreadInstance, check for the String element and add its value to our function list e.g.
# => Add-IcingaForWindowsDaemon
if ($ThreadFetchNext) {
if ($entry.Type -eq 'String') {
if (Test-IcingaFunction $entry.Content) {
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
$FunctionList += [string]$entry.Content;
$FncCache.Add($entry.Content, 0);
}
}
}
$ThreadFetchNext = $FALSE;
}
# If we found the command New-IcingaThreadInstance inside ths script, loop until we reach -Command
if ($ThreadCommand) {
if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-command') {
$ThreadFetchNext = $TRUE;
$ThreadCommand = $FALSE;
}
}
# If we reached the string content of our .AddCommand() object. add its value to our function list e.g.
# => Set-IcingaEnvironmentGlobal
if ($ShellGroupStart) {
if ($entry.Type -eq 'String') {
if (Test-IcingaFunction $entry.Content) {
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
$FunctionList += [string]$entry.Content;
$FncCache.Add($entry.Content, 0);
}
}
$ShellGroupStart = $FALSE;
}
}
# If we found an .AddArgument() member, continue until our group starts with (
if ($ShellCommand) {
if ($entry.Type -eq 'GroupStart' -And $entry.Content.ToLower() -eq '(') {
$ShellCommand = $FALSE;
$ShellGroupStart = $TRUE;
}
} }
$Index += 1; $Index += 1;

View file

@ -0,0 +1,24 @@
function Set-IcingaForWindowsServiceJEAProfile()
{
[string]$JeaProfile = Get-IcingaJEAContext;
$IcingaForWindowsService = Get-IcingaForWindowsServiceData;
if ([string]::IsNullOrEmpty($IcingaForWindowsService.FullPath) -Or (Test-Path $IcingaForWindowsService.FullPath) -eq $FALSE) {
return;
}
[string]$PreparedServicePath = [string]::Format(
'\"{0}\" \"{1}\" \"{2}\"',
$IcingaForWindowsService.FullPath,
(Get-IcingaPowerShellModuleFile),
$JeaProfile
);
$Result = Start-IcingaProcess -Executable 'sc.exe' -Arguments ([string]::Format('config icingapowershell binPath= "{0}"', $PreparedServicePath));
if ($Result.ExitCode -ne 0) {
Write-IcingaConsoleError 'Failed to update Icinga for Windows service for JEA profile "{0}": {1}{2}' -Objects $JeaProfile, $ResolveStatus.Message, $ResolveStatus.Error;
} else {
Write-IcingaConsoleNotice 'Icinga for Windows service JEA handling has been configured successfully to profile "{0}"' -Objects $JeaProfile;
}
}

View file

@ -45,6 +45,12 @@ function New-IcingaThreadInstance()
[void]$Shell.AddParameter('GlobalEnvironment', $Global:Icinga.Public); [void]$Shell.AddParameter('GlobalEnvironment', $Global:Icinga.Public);
} }
# Set the JEA context for all threads
if ($null -ne $Global:Icinga -And $Global:Icinga.ContainsKey('Protected') -And $Global:Icinga.Protected.ContainsKey('JEAContext')) {
[void]$Shell.AddCommand('Set-IcingaEnvironmentJEA');
[void]$Shell.AddParameter('JeaEnabled', $Global:Icinga.Protected.JEAContext);
}
[void]$Shell.AddCommand($Command); [void]$Shell.AddCommand($Command);
$CodeHash = $Command; $CodeHash = $Command;

View file

@ -9,7 +9,7 @@ function New-IcingaThreadPool()
$SessionFile = Get-IcingaJEASessionFile; $SessionFile = Get-IcingaJEASessionFile;
if ([string]::IsNullOrEmpty((Get-IcingaJEAContext))) { if ([string]::IsNullOrEmpty((Get-IcingaJEAContext))) {
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault(); $SessionConfiguration = [System.Management.Automation.RunSpaces.InitialSessionState]::CreateDefault();
} else { } else {
if ([string]::IsNullOrEmpty($SessionFile)) { if ([string]::IsNullOrEmpty($SessionFile)) {
Write-IcingaEventMessage -EventId 1502 -Namespace 'Framework'; Write-IcingaEventMessage -EventId 1502 -Namespace 'Framework';
@ -18,14 +18,14 @@ function New-IcingaThreadPool()
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateFromSessionConfigurationFile($SessionFile); $SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateFromSessionConfigurationFile($SessionFile);
} }
$Runspaces = [RunspaceFactory]::CreateRunspacePool( $RunSpaces = [RunSpaceFactory]::CreateRunSpacePool(
$MinInstances, $MinInstances,
$MaxInstances, $MaxInstances,
$SessionConfiguration, $SessionConfiguration,
$host $host
) )
$Runspaces.Open(); $RunSpaces.Open();
return $Runspaces; return $RunSpaces;
} }

View file

@ -0,0 +1,10 @@
function Set-IcingaEnvironmentJEA()
{
param (
[bool]$JeaEnabled = $FALSE
);
if ($null -ne $Global:Icinga -And $Global:Icinga.ContainsKey('Protected') -And $Global:Icinga.Protected.ContainsKey('JEAContext')) {
$Global:Icinga.Protected.JEAContext = $JeaEnabled;
}
}

View file

@ -2,20 +2,23 @@ function Start-IcingaPowerShellDaemon()
{ {
param ( param (
[switch]$RunAsService = $FALSE, [switch]$RunAsService = $FALSE,
[switch]$JEAContext = $FALSE,
[switch]$JEARestart = $FALSE [switch]$JEARestart = $FALSE
); );
Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEARestart:$JEARestart; Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEARestart:$JEARestart -JEAContext:$JEAContext;
} }
function Start-IcingaForWindowsDaemon() function Start-IcingaForWindowsDaemon()
{ {
param ( param (
[switch]$RunAsService = $FALSE, [switch]$RunAsService = $FALSE,
[switch]$JEAContext = $FALSE,
[switch]$JEARestart = $FALSE [switch]$JEARestart = $FALSE
); );
$Global:Icinga.Protected.RunAsDaemon = $TRUE; $Global:Icinga.Protected.RunAsDaemon = [bool]$RunAsService;
$Global:Icinga.Protected.JEAContext = [bool]$JEAContext;
[string]$MainServicePidFile = (Join-Path -Path (Get-IcingaCacheDir) -ChildPath 'service.pid'); [string]$MainServicePidFile = (Join-Path -Path (Get-IcingaCacheDir) -ChildPath 'service.pid');
[string]$JeaPidFile = (Join-Path -Path (Get-IcingaCacheDir) -ChildPath 'jea.pid'); [string]$JeaPidFile = (Join-Path -Path (Get-IcingaCacheDir) -ChildPath 'jea.pid');
[string]$JeaProfile = Get-IcingaPowerShellConfig -Path 'Framework.JEAProfile'; [string]$JeaProfile = Get-IcingaPowerShellConfig -Path 'Framework.JEAProfile';
@ -32,7 +35,6 @@ function Start-IcingaForWindowsDaemon()
if ([string]::IsNullOrEmpty($JeaProfile)) { if ([string]::IsNullOrEmpty($JeaProfile)) {
Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service without JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile; Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service without JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile;
$Global:Icinga.Protected.RunAsDaemon = $TRUE;
# Todo: Add config for active background tasks. Set it to 20 for the moment # Todo: Add config for active background tasks. Set it to 20 for the moment
Add-IcingaThreadPool -Name 'MainPool' -MaxInstances 20; Add-IcingaThreadPool -Name 'MainPool' -MaxInstances 20;
$Global:Icinga.Public.Add('SSLCertificate', $Certificate); $Global:Icinga.Public.Add('SSLCertificate', $Certificate);
@ -41,6 +43,7 @@ function Start-IcingaForWindowsDaemon()
New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start; New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start;
} else { } else {
Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service inside JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile; Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service inside JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile;
& powershell.exe -NoProfile -NoLogo -ConfigurationName $JeaProfile -Command { & powershell.exe -NoProfile -NoLogo -ConfigurationName $JeaProfile -Command {
try { try {
Use-Icinga -Daemon; Use-Icinga -Daemon;
@ -84,7 +87,7 @@ function Start-IcingaForWindowsDaemon()
Write-IcingaFileSecure -File $JeaPidFile -Value ''; Write-IcingaFileSecure -File $JeaPidFile -Value '';
Write-IcingaEventMessage -EventId 1505 -Namespace Framework -Objects ([string]::Format('{0}/5', $JeaRestartCounter)); Write-IcingaEventMessage -EventId 1505 -Namespace Framework -Objects ([string]::Format('{0}/5', $JeaRestartCounter));
Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEARestart; Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEAContext:$JEAContext -JEARestart;
$JeaRestartCounter += 1; $JeaRestartCounter += 1;
$JeaPid = ''; $JeaPid = '';

View file

@ -9,10 +9,6 @@ function New-IcingaForWindowsRESTApi()
$RequireAuth $RequireAuth
); );
# Import the framework library components and initialise it
# as daemon
Use-Icinga -LibOnly -Daemon;
$RESTEndpoints = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTAPIEndpoint*'; $RESTEndpoints = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTAPIEndpoint*';
Write-IcingaDebugMessage -Message ( Write-IcingaDebugMessage -Message (
[string]::Format( [string]::Format(

View file

@ -5,10 +5,6 @@ function New-IcingaForWindowsRESTThread()
$ThreadId $ThreadId
); );
# Import the framework library components and initialise it
# as daemon
Use-Icinga -LibOnly -Daemon;
# Initialise our performance counter categories # Initialise our performance counter categories
Show-IcingaPerformanceCounterCategories | Out-Null; Show-IcingaPerformanceCounterCategories | Out-Null;