Skip to content

Commit ca1eab8

Browse files
authored
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.
2 parents e3e330f + 954e69f commit ca1eab8

13 files changed

+149
-35
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ cache/*
66
.vscode/
77
.vs/
88
*.log
9+
*.pfx
910

1011
# JEA
1112
RoleCapabilities/IcingaForWindows.psrc

doc/100-General/10-Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
3535
* [#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
3636
* [#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
3737
* [#452](https://github.com/Icinga/icinga-powershell-framework/pull/452) Fixes unhandled `true` output on the console while running the installer
38+
* [#454](https://github.com/Icinga/icinga-powershell-framework/pull/454) Fixes JEA catalog compiler and background daemon execution in JEA context
3839

3940
### Enhancements
4041

lib/core/dev/New-IcingaForWindowsComponent.psm1

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,6 @@ function New-IcingaForWindowsComponent()
172172
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.';
173173
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/';
174174
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value '';
175-
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' Use-Icinga -LibOnly -Daemon;';
176-
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value '';
177175
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' while ($TRUE) {';
178176
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' # Add your daemon code within this loop';
179177
Add-Content -Path (Join-Path -Path $ModuleDir -ChildPath ([string]::Format('daemon\{0}.psm1', $DaemonEntry))) -Value ' Start-Sleep -Seconds 1;';

lib/core/jea/Get-IcingaCommandDependency.psm1

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,57 @@ function Get-IcingaCommandDependency()
77
[string]$CmdType = ''
88
);
99

10+
# Function, Cmdlet, Alias, Modules, Application
1011
if ([string]::IsNullOrEmpty($CmdType)) {
1112
return $CompiledList;
1213
}
1314

15+
# Create the list container for our object type if not existing
16+
# => Function, Cmdlet, Alias, Modules, Application
1417
if ($CompiledList.ContainsKey($CmdType) -eq $FALSE) {
1518
$CompiledList.Add($CmdType, @{ });
1619
}
1720

21+
# e.g. Invoke-IcingaCheckCPU
1822
if ($CompiledList[$CmdType].ContainsKey($CmdName)) {
1923
$CompiledList[$CmdType][$CmdName] += 1;
24+
2025
return $CompiledList;
2126
}
2227

28+
# Add the command this function is called with
2329
$CompiledList[$CmdType].Add($CmdName, 0);
2430

31+
# The command is not known in our Framework dependency list -> could be a native Windows command
2532
if ((Test-PSCustomObjectMember -PSObject $DependencyList -Name $CmdName) -eq $FALSE) {
2633
return $CompiledList;
2734
}
2835

36+
# Loop our entire dependency list for every single command
2937
foreach ($CmdList in $DependencyList.$CmdName.PSObject.Properties.Name) {
38+
# $Cmd => The list of child commands
39+
# $CmdList => Function, Cmdlet, Alias, Modules, Application
3040
$Cmd = $DependencyList.$CmdName.$CmdList;
3141

42+
# Create the list container for our object type if not existing
43+
# => Function, Cmdlet, Alias, Modules, Application
3244
if ($CompiledList.ContainsKey($CmdList) -eq $FALSE) {
3345
$CompiledList.Add($CmdList, @{ });
3446
}
3547

48+
# Loop all commands within our child list for this command
3649
foreach ($entry in $Cmd.PSObject.Properties.Name) {
37-
if ($CompiledList[$CmdList].ContainsKey($entry) -eq $FALSE) {
38-
$CompiledList[$CmdList].Add($entry, 0);
3950

51+
# $entry => The command name e.g. Write-IcingaConsolePlain
52+
if ($CompiledList[$CmdList].ContainsKey($entry) -eq $FALSE) {
4053
$CompiledList = Get-IcingaCommandDependency `
4154
-DependencyList $DependencyList `
4255
-CompiledList $CompiledList `
43-
-CmdName $entry;
44-
} else {
45-
$CompiledList[$CmdList][$entry] += 1;
46-
}
56+
-CmdName $entry `
57+
-CmdType $CmdList;
58+
} else {
59+
$CompiledList[$CmdList][$entry] += 1;
60+
}
4761
}
4862
}
4963

lib/core/jea/Get-IcingaJEAConfiguration.psm1

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,17 @@ function Get-IcingaJEAConfiguration()
147147
-CmdType 'Function';
148148

149149
# We need to add this function for our background daemon we start with 'Start-IcingaForWindowsDaemon',
150-
# as this function is called outside the JEA context
150+
# as these functions are called outside the JEA context
151151
$UsedCmdlets = Get-IcingaCommandDependency `
152152
-DependencyList $DependencyList `
153153
-CompiledList $UsedCmdlets `
154-
-CmdName 'Add-IcingaForWindowsDaemon' `
154+
-CmdName 'Start-IcingaPowerShellDaemon' `
155+
-CmdType 'Function';
156+
157+
$UsedCmdlets = Get-IcingaCommandDependency `
158+
-DependencyList $DependencyList `
159+
-CompiledList $UsedCmdlets `
160+
-CmdName 'Start-IcingaForWindowsDaemon' `
155161
-CmdType 'Function';
156162

157163
# Fixes error if only the Icinga PowerShell Framework is installed, which then causes JEA to fail entirely because of this missing Cmdlet

lib/core/jea/Read-IcingaPowerShellModuleFile.psm1

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@ function Read-IcingaPowerShellModuleFile()
1313
$FileContent = Read-IcingaFileSecure -File $File;
1414
}
1515

16-
$PSParser = [System.Management.Automation.PSParser]::Tokenize($FileContent, [ref]$null);
17-
[array]$Comments = @();
18-
[array]$RegexFilter = @();
19-
[string]$RegexPattern = '';
20-
[array]$CommandList = @();
21-
[array]$FunctionList = @();
22-
[hashtable]$CmdCache = @{ };
23-
[hashtable]$FncCache = @{ };
24-
[int]$Index = 0;
16+
$PSParser = [System.Management.Automation.PSParser]::Tokenize($FileContent, [ref]$null);
17+
[array]$Comments = @();
18+
[array]$RegexFilter = @();
19+
[string]$RegexPattern = '';
20+
[array]$CommandList = @();
21+
[array]$FunctionList = @();
22+
[hashtable]$CmdCache = @{ };
23+
[hashtable]$FncCache = @{ };
24+
[int]$Index = 0;
25+
[bool]$ThreadCommand = $FALSE;
26+
[bool]$ThreadFetchNext = $FALSE;
27+
[bool]$ShellCommand = $FALSE;
28+
[bool]$ShellGroupStart = $FALSE;
2529

2630
foreach ($entry in $PSParser) {
2731
if ($entry.Type -eq 'Comment') {
@@ -31,13 +35,68 @@ function Read-IcingaPowerShellModuleFile()
3135
$CommandList += [string]$entry.Content;
3236
$CmdCache.Add($entry.Content, 0);
3337
}
38+
39+
# We need to include commands we call with New-IcingaThreadInstance e.g.
40+
# => New-IcingaThreadInstance -Name "Main" -ThreadPool (Get-IcingaThreadPool -Name 'MainPool') -Command 'Add-IcingaForWindowsDaemon' -Start;
41+
if ($entry.Content.ToLower() -eq 'new-icingathreadinstance') {
42+
$ThreadCommand = $TRUE;
43+
}
3444
} elseif ($entry.Type -eq 'CommandArgument') {
3545
if ($PSParser[$index - 1].Type -eq 'Keyword' -And $PSParser[$index - 1].Content.ToLower() -eq 'function') {
3646
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
3747
$FunctionList += [string]$entry.Content;
3848
$FncCache.Add($entry.Content, 0);
3949
}
4050
}
51+
} elseif ($entry.Type -eq 'Member' -And $entry.Content.ToLower() -eq 'addcommand') {
52+
# In case we have objects that use .AddCommand() we should add these to our function list e.g.
53+
# => [void]$Shell.AddCommand('Set-IcingaEnvironmentGlobal');
54+
$ShellCommand = $TRUE;
55+
}
56+
57+
# If we reached -Command for New-IcingaThreadInstance, check for the String element and add its value to our function list e.g.
58+
# => Add-IcingaForWindowsDaemon
59+
if ($ThreadFetchNext) {
60+
if ($entry.Type -eq 'String') {
61+
if (Test-IcingaFunction $entry.Content) {
62+
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
63+
$FunctionList += [string]$entry.Content;
64+
$FncCache.Add($entry.Content, 0);
65+
}
66+
}
67+
}
68+
$ThreadFetchNext = $FALSE;
69+
}
70+
71+
# If we found the command New-IcingaThreadInstance inside ths script, loop until we reach -Command
72+
if ($ThreadCommand) {
73+
if ($entry.Type -eq 'CommandParameter' -And $entry.Content.ToLower() -eq '-command') {
74+
$ThreadFetchNext = $TRUE;
75+
$ThreadCommand = $FALSE;
76+
}
77+
}
78+
79+
# If we reached the string content of our .AddCommand() object. add its value to our function list e.g.
80+
# => Set-IcingaEnvironmentGlobal
81+
if ($ShellGroupStart) {
82+
if ($entry.Type -eq 'String') {
83+
if (Test-IcingaFunction $entry.Content) {
84+
if ($FncCache.ContainsKey($entry.Content) -eq $FALSE) {
85+
$FunctionList += [string]$entry.Content;
86+
$FncCache.Add($entry.Content, 0);
87+
}
88+
}
89+
90+
$ShellGroupStart = $FALSE;
91+
}
92+
}
93+
94+
# If we found an .AddArgument() member, continue until our group starts with (
95+
if ($ShellCommand) {
96+
if ($entry.Type -eq 'GroupStart' -And $entry.Content.ToLower() -eq '(') {
97+
$ShellCommand = $FALSE;
98+
$ShellGroupStart = $TRUE;
99+
}
41100
}
42101

43102
$Index += 1;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function Set-IcingaForWindowsServiceJEAProfile()
2+
{
3+
[string]$JeaProfile = Get-IcingaJEAContext;
4+
$IcingaForWindowsService = Get-IcingaForWindowsServiceData;
5+
6+
if ([string]::IsNullOrEmpty($IcingaForWindowsService.FullPath) -Or (Test-Path $IcingaForWindowsService.FullPath) -eq $FALSE) {
7+
return;
8+
}
9+
10+
[string]$PreparedServicePath = [string]::Format(
11+
'\"{0}\" \"{1}\" \"{2}\"',
12+
$IcingaForWindowsService.FullPath,
13+
(Get-IcingaPowerShellModuleFile),
14+
$JeaProfile
15+
);
16+
17+
$Result = Start-IcingaProcess -Executable 'sc.exe' -Arguments ([string]::Format('config icingapowershell binPath= "{0}"', $PreparedServicePath));
18+
19+
if ($Result.ExitCode -ne 0) {
20+
Write-IcingaConsoleError 'Failed to update Icinga for Windows service for JEA profile "{0}": {1}{2}' -Objects $JeaProfile, $ResolveStatus.Message, $ResolveStatus.Error;
21+
} else {
22+
Write-IcingaConsoleNotice 'Icinga for Windows service JEA handling has been configured successfully to profile "{0}"' -Objects $JeaProfile;
23+
}
24+
}

lib/core/thread/New-IcingaThreadInstance.psm1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ function New-IcingaThreadInstance()
4545
[void]$Shell.AddParameter('GlobalEnvironment', $Global:Icinga.Public);
4646
}
4747

48+
# Set the JEA context for all threads
49+
if ($null -ne $Global:Icinga -And $Global:Icinga.ContainsKey('Protected') -And $Global:Icinga.Protected.ContainsKey('JEAContext')) {
50+
[void]$Shell.AddCommand('Set-IcingaEnvironmentJEA');
51+
[void]$Shell.AddParameter('JeaEnabled', $Global:Icinga.Protected.JEAContext);
52+
}
53+
4854
[void]$Shell.AddCommand($Command);
4955

5056
$CodeHash = $Command;

lib/core/thread/New-IcingaThreadPool.psm1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function New-IcingaThreadPool()
99
$SessionFile = Get-IcingaJEASessionFile;
1010

1111
if ([string]::IsNullOrEmpty((Get-IcingaJEAContext))) {
12-
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault();
12+
$SessionConfiguration = [System.Management.Automation.RunSpaces.InitialSessionState]::CreateDefault();
1313
} else {
1414
if ([string]::IsNullOrEmpty($SessionFile)) {
1515
Write-IcingaEventMessage -EventId 1502 -Namespace 'Framework';
@@ -18,14 +18,14 @@ function New-IcingaThreadPool()
1818
$SessionConfiguration = [System.Management.Automation.Runspaces.InitialSessionState]::CreateFromSessionConfigurationFile($SessionFile);
1919
}
2020

21-
$Runspaces = [RunspaceFactory]::CreateRunspacePool(
21+
$RunSpaces = [RunSpaceFactory]::CreateRunSpacePool(
2222
$MinInstances,
2323
$MaxInstances,
2424
$SessionConfiguration,
2525
$host
2626
)
2727

28-
$Runspaces.Open();
28+
$RunSpaces.Open();
2929

30-
return $Runspaces;
30+
return $RunSpaces;
3131
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function Set-IcingaEnvironmentJEA()
2+
{
3+
param (
4+
[bool]$JeaEnabled = $FALSE
5+
);
6+
7+
if ($null -ne $Global:Icinga -And $Global:Icinga.ContainsKey('Protected') -And $Global:Icinga.Protected.ContainsKey('JEAContext')) {
8+
$Global:Icinga.Protected.JEAContext = $JeaEnabled;
9+
}
10+
}

0 commit comments

Comments
 (0)