Reworks background daemon for JEA context

This commit is contained in:
Lord Hepipud 2022-01-28 13:50:24 +01:00
parent e3e330f5d3
commit 954e69fde1
13 changed files with 149 additions and 35 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ cache/*
.vscode/
.vs/
*.log
*.pfx
# JEA
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
* [#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
* [#454](https://github.com/Icinga/icinga-powershell-framework/pull/454) Fixes JEA catalog compiler and background daemon execution in JEA context
### 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 ' # 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 ' 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 ' # 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;';

View file

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

View file

@ -147,11 +147,17 @@ function Get-IcingaJEAConfiguration()
-CmdType 'Function';
# 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 `
-DependencyList $DependencyList `
-CompiledList $UsedCmdlets `
-CmdName 'Add-IcingaForWindowsDaemon' `
-CmdName 'Start-IcingaPowerShellDaemon' `
-CmdType 'Function';
$UsedCmdlets = Get-IcingaCommandDependency `
-DependencyList $DependencyList `
-CompiledList $UsedCmdlets `
-CmdName 'Start-IcingaForWindowsDaemon' `
-CmdType 'Function';
# 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]$FncCache = @{ };
[int]$Index = 0;
[bool]$ThreadCommand = $FALSE;
[bool]$ThreadFetchNext = $FALSE;
[bool]$ShellCommand = $FALSE;
[bool]$ShellGroupStart = $FALSE;
foreach ($entry in $PSParser) {
if ($entry.Type -eq 'Comment') {
@ -31,6 +35,12 @@ function Read-IcingaPowerShellModuleFile()
$CommandList += [string]$entry.Content;
$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') {
if ($PSParser[$index - 1].Type -eq 'Keyword' -And $PSParser[$index - 1].Content.ToLower() -eq 'function') {
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
@ -38,6 +48,55 @@ function Read-IcingaPowerShellModuleFile()
$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;

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);
}
# 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);
$CodeHash = $Command;

View file

@ -9,7 +9,7 @@ function New-IcingaThreadPool()
$SessionFile = Get-IcingaJEASessionFile;
if ([string]::IsNullOrEmpty((Get-IcingaJEAContext))) {
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault();
$SessionConfiguration = [System.Management.Automation.RunSpaces.InitialSessionState]::CreateDefault();
} else {
if ([string]::IsNullOrEmpty($SessionFile)) {
Write-IcingaEventMessage -EventId 1502 -Namespace 'Framework';
@ -18,14 +18,14 @@ function New-IcingaThreadPool()
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateFromSessionConfigurationFile($SessionFile);
}
$Runspaces = [RunspaceFactory]::CreateRunspacePool(
$RunSpaces = [RunSpaceFactory]::CreateRunSpacePool(
$MinInstances,
$MaxInstances,
$SessionConfiguration,
$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 (
[switch]$RunAsService = $FALSE,
[switch]$JEAContext = $FALSE,
[switch]$JEARestart = $FALSE
);
Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEARestart:$JEARestart;
Start-IcingaForWindowsDaemon -RunAsService:$RunAsService -JEARestart:$JEARestart -JEAContext:$JEAContext;
}
function Start-IcingaForWindowsDaemon()
{
param (
[switch]$RunAsService = $FALSE,
[switch]$JEAContext = $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]$JeaPidFile = (Join-Path -Path (Get-IcingaCacheDir) -ChildPath 'jea.pid');
[string]$JeaProfile = Get-IcingaPowerShellConfig -Path 'Framework.JEAProfile';
@ -32,7 +35,6 @@ function Start-IcingaForWindowsDaemon()
if ([string]::IsNullOrEmpty($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
Add-IcingaThreadPool -Name 'MainPool' -MaxInstances 20;
$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;
} else {
Write-IcingaDebugMessage -Message 'Starting Icinga for Windows service inside JEA context' -Objects $RunAsService, $JEARestart, $JeaProfile;
& powershell.exe -NoProfile -NoLogo -ConfigurationName $JeaProfile -Command {
try {
Use-Icinga -Daemon;
@ -84,7 +87,7 @@ function Start-IcingaForWindowsDaemon()
Write-IcingaFileSecure -File $JeaPidFile -Value '';
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;
$JeaPid = '';

View file

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

View file

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