Skip to content

Commit 694c31c

Browse files
authored
Merge pull request #455 from Icinga:feature/support_check_by_icingaforwindows
Feature: Adds support for check_by_icingaforwindows.ps1 Adds support for the remote execution plugin [check_by_icingaforwindows](https://github.com/LordHepipud/check_by_icingaforwindows), allowing to check from a Linux or Windows remote host by using WinRM to a target Windows machine. Fully supports JEA profiles, but requires powershell installed on the Linux machine.
2 parents ca1eab8 + e0fc3e5 commit 694c31c

File tree

4 files changed

+138
-25
lines changed

4 files changed

+138
-25
lines changed

doc/100-General/10-Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
4747
* [#445](https://github.com/Icinga/icinga-powershell-framework/pull/445) Adds command `Repair-IcingaService` to repair Icinga Agent service in case it was broken during upgrades, mostly caused by `The specified service has been marked for deletion`
4848
* [#448](https://github.com/Icinga/icinga-powershell-framework/pull/448) Adds support to sort arrays without ScriptBlocks
4949
* [#450](https://github.com/Icinga/icinga-powershell-framework/pull/450) Improves show command `Show-IcingaRegisteredServiceChecks`, adds new command `Show-IcingaRegisteredBackgroundDaemons` and extends `Show-Icinga` by both commands and adds debug and api forwarder features to environment list
50+
* [#455](https://github.com/Icinga/icinga-powershell-framework/pull/455) Adds support for remote execution plugin [check_by_icingaforwindows](https://github.com/LordHepipud/check_by_icingaforwindows)
5051

5152
## 1.7.1 (2021-11-11)
5253

lib/core/framework/Invoke-IcingaInternalServiceCall.psm1

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@ function Invoke-IcingaInternalServiceCall()
22
{
33
param (
44
[string]$Command = '',
5-
[array]$Arguments = @()
5+
[array]$Arguments = @(),
6+
[switch]$NoExit = $FALSE
67
);
78

89
# If our Framework is running as daemon, never call our api
910
if ($Global:Icinga.Protected.RunAsDaemon) {
10-
return;
11+
return $NULL;
1112
}
1213

1314
# If the API forward feature is disabled, do nothing
1415
if ((Get-IcingaFrameworkApiChecks) -eq $FALSE) {
15-
return;
16+
return $NULL;
1617
}
1718

1819
# Test our Icinga for Windows service. If the service is not installed or not running, execute the plugin locally
1920
$IcingaForWindowsService = (Get-Service 'icingapowershell' -ErrorAction SilentlyContinue);
2021

2122
if ($null -eq $IcingaForWindowsService -Or $IcingaForWindowsService.Status -ne 'Running') {
22-
return;
23+
return $NULL;
2324
}
2425

2526
# In case the REST-Api module ist not configured, do nothing
2627
$BackgroundDaemons = Get-IcingaBackgroundDaemons;
2728

2829
if ($null -eq $BackgroundDaemons -Or $BackgroundDaemons.ContainsKey('Start-IcingaWindowsRESTApi') -eq $FALSE) {
29-
return;
30+
return $NULL;
3031
}
3132

3233
$RestApiPort = 5668;
@@ -57,6 +58,11 @@ function Invoke-IcingaInternalServiceCall()
5758
[string]$Argument = [string]$Value;
5859
$ArgumentValue = $null;
5960

61+
if ($Argument -eq '-IcingaForWindowsRemoteExecution' -Or $Argument -eq '-IcingaForWindowsJEARemoteExecution') {
62+
$ArgumentIndex += 1;
63+
continue;
64+
}
65+
6066
if ($Value[0] -eq '-') {
6167
if (($ArgumentIndex + 1) -lt $Arguments.Count) {
6268
[string]$NextValue = $Arguments[$ArgumentIndex + 1];
@@ -85,7 +91,7 @@ function Invoke-IcingaInternalServiceCall()
8591
} catch {
8692
# Fallback to execute plugin locally
8793
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -ExceptionObject $_ -Objects $Command, $CommandArguments;
88-
return;
94+
return $NULL;
8995
}
9096

9197
# Resolve our result from the API
@@ -95,12 +101,12 @@ function Invoke-IcingaInternalServiceCall()
95101
# In case we didn't receive a check result, fallback to local execution
96102
if ([string]::IsNullOrEmpty($IcingaResult.$Command.checkresult)) {
97103
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects 'The check result for the executed command was empty', $Command, $CommandArguments;
98-
return;
104+
return $NULL;
99105
}
100106

101107
if ([string]::IsNullOrEmpty($IcingaResult.$Command.exitcode)) {
102108
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects 'The check result for the executed command was empty', $Command, $CommandArguments;
103-
return;
109+
return $NULL;
104110
}
105111

106112
$IcingaCR = ($IcingaResult.$Command.checkresult.Replace("`r`n", "`n"));
@@ -112,6 +118,12 @@ function Invoke-IcingaInternalServiceCall()
112118
}
113119
}
114120

121+
if ($NoExit) {
122+
Set-IcingaInternalPluginExitCode -ExitCode $IcingaResult.$Command.exitcode;
123+
124+
return $IcingaCR;
125+
}
126+
115127
# Print our response and exit with the provide exit code
116128
Write-IcingaConsolePlain $IcingaCR;
117129
exit $IcingaResult.$Command.exitcode;

lib/icinga/plugin/Exit-IcingaExecutePlugin.psm1

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,103 @@ function Exit-IcingaExecutePlugin()
44
[string]$Command = ''
55
);
66

7-
$JEAProfile = Get-IcingaJEAContext;
7+
[string]$JEAProfile = Get-IcingaJEAContext;
8+
[bool]$CheckByIcingaForWindows = $FALSE;
9+
[bool]$CheckByJEAShell = $FALSE;
810

11+
if ($args -Contains '-IcingaForWindowsRemoteExecution') {
12+
$CheckByIcingaForWindows = $TRUE;
13+
}
14+
if ($args -Contains '-IcingaForWindowsJEARemoteExecution') {
15+
$CheckByJEAShell = $TRUE;
16+
}
17+
18+
# We use the plugin check_by_icingaforwindows.ps1 to execute
19+
# checks from a Linux/Windows remote source
20+
if ($CheckByIcingaForWindows) {
21+
# First try to queue the check over the REST-Api
22+
$CheckResult = Invoke-IcingaInternalServiceCall -Command $Command -Arguments $args -NoExit;
23+
24+
if ($null -ne $CheckResult) {
25+
# Seems we got a result
26+
Write-IcingaConsolePlain -Message $CheckResult;
27+
28+
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
29+
# The plugin itself will terminate the session
30+
return;
31+
}
32+
33+
# We couldn't use our Rest-Api and Api-Checks feature, then lets execute the plugin locally
34+
# Set daemon true, because this will change internal handling for errors and plugin output
35+
$Global:Icinga.Protected.RunAsDaemon = $TRUE;
36+
37+
try {
38+
# Execute our plugin
39+
(& $Command @args) | Out-Null;
40+
} catch {
41+
# Handle errors within our plugins
42+
# If anything goes wrong handle the error very detailed
43+
44+
$Global:Icinga.Protected.RunAsDaemon = $FALSE;
45+
Write-IcingaExecutePluginException -Command $Command -ErrorObject $_ -Arguments $args;
46+
$args.Clear();
47+
48+
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
49+
# The plugin itself will terminate the session
50+
return;
51+
}
52+
53+
# Disable it again - we need to write data to our shell now. Not very intuitive, but it is the easiest
54+
# solution to do it this way
55+
$Global:Icinga.Protected.RunAsDaemon = $FALSE;
56+
57+
# Now print the result to shell
58+
Write-IcingaPluginResult -PluginOutput (Get-IcingaInternalPluginOutput) -PluginPerfData (Get-IcingaCheckSchedulerPerfData);
59+
60+
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
61+
# The plugin itself will terminate the session
62+
return;
63+
}
64+
65+
# Regardless of JEA enabled or disabled, forward all checks to the internal API
66+
# and check if we get a result from there
967
Invoke-IcingaInternalServiceCall -Command $Command -Arguments $args;
1068

1169
try {
70+
# If the plugin is not installed, throw a good exception
1271
Exit-IcingaPluginNotInstalled -Command $Command;
1372

73+
# In case we have JEA enabled on our system, this shell currently open most likely has no
74+
# JEA configuration installed. This is because a JEA shell will not return an exit code and
75+
# Icinga relies on that. Therefor we will try to open a new PowerShell with the JEA configuration
76+
# assigned for Icinga for Windows, execute the plugins there and return the result
1477
if ([string]::IsNullOrEmpty($JEAProfile) -eq $FALSE) {
1578
$ErrorHandler = ''
1679
$JEARun = (
1780
& powershell.exe -ConfigurationName $JEAProfile -NoLogo -NoProfile -Command {
81+
# Load Icinga for Windows
1882
Use-Icinga;
1983

84+
# Enable our JEA context
2085
$Global:Icinga.Protected.JEAContext = $TRUE;
2186

87+
# Parse the arguments our previous shell received
2288
$Command = $args[0];
2389
$Arguments = $args[1];
2490
$Output = '';
2591

2692
try {
93+
# Try executing our checks, store the exit code and plugin output
2794
$ExitCode = (& $Command @Arguments);
2895
$Output = (Get-IcingaInternalPluginOutput);
2996
$ExitCode = (Get-IcingaInternalPluginExitCode);
3097
} catch {
98+
# If we failed for some reason, print a detailed error and use exit code 3 to mark the check as unkown
3199
$Output = [string]::Format('[UNKNOWN] Icinga Exception: Error while executing plugin in JEA context{0}{0}{1}', (New-IcingaNewLine), $_.Exception.Message);
32100
$ExitCode = 3;
33101
}
34102

103+
# Return the result to our main PowerShell
35104
return @{
36105
'Output' = $Output;
37106
'PerfData' = (Get-IcingaCheckSchedulerPerfData)
@@ -40,34 +109,27 @@ function Exit-IcingaExecutePlugin()
40109
} -args $Command, $args
41110
) 2>$ErrorHandler;
42111

112+
# If we have an exit code larger or equal 0, the execution inside the JEA shell was successfully and we can share the result
113+
# In case we had an error inside the JEA shell, it will returned here as well
43114
if ($LASTEXITCODE -ge 0) {
44115
Write-IcingaPluginResult -PluginOutput $JEARun.Output -PluginPerfData $JEARun.PerfData;
45116
exit $JEARun.ExitCode;
46117
} else {
118+
# If for some reason the PowerShell could not be started within JEA context, we can throw an exception with exit code 3
119+
# to mark the check as unknown including our error message
47120
Write-IcingaConsolePlain '[UNKNOWN] Icinga Exception: Unable to start the PowerShell.exe with the provided JEA profile "{0}" for CheckCommand: {1}' -Objects $JEAProfile, $Command;
48121
exit 3;
49122
}
50123
} else {
124+
# If we simply run the check without JEA context or from remote, we can just execute the plugin and
125+
# exit with the exit code received from the result
51126
exit (& $Command @args);
52127
}
53128
} catch {
54-
$ExMsg = $_.Exception.Message;
55-
$StackTrace = $_.ScriptStackTrace;
56-
$ExErrorId = $_.FullyQualifiedErrorId;
57-
$ArgName = $_.Exception.ParameterName;
58-
$ListArgs = $args;
59-
60-
if ($ExErrorId -Like "*ParameterArgumentTransformationError*" -And $ExMsg.Contains('System.Security.SecureString')) {
61-
$ExMsg = [string]::Format(
62-
'Cannot bind parameter {0}. Cannot convert the provided value for argument "{0}" of type "System.String" to type "System.Security.SecureString".',
63-
$ArgName
64-
);
65-
66-
$args.Clear();
67-
$ListArgs = 'Hidden for security reasons';
68-
}
129+
# If anything goes wrong handle the error
130+
Write-IcingaExecutePluginException -Command $Command -ErrorObject $_ -Arguments $args;
131+
$args.Clear();
69132

70-
Write-IcingaConsolePlain '[UNKNOWN] Icinga Exception: {0}{1}{1}CheckCommand: {2}{1}Arguments: {3}{1}{1}StackTrace:{1}{4}' -Objects $ExMsg, (New-IcingaNewLine), $Command, $ListArgs, $StackTrace;
71133
exit 3;
72134
}
73135
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function Write-IcingaExecutePluginException()
2+
{
3+
param (
4+
$Command = '',
5+
$ErrorObject = $null,
6+
$Arguments = @()
7+
);
8+
9+
if ($null -eq $ErrorObject) {
10+
return;
11+
}
12+
13+
$ExMsg = $ErrorObject.Exception.Message;
14+
$StackTrace = $ErrorObject.ScriptStackTrace;
15+
$ExErrorId = $ErrorObject.FullyQualifiedErrorId;
16+
$ArgName = $ErrorObject.Exception.ParameterName;
17+
$ListArgs = @();
18+
19+
foreach ($entry in $Arguments) {
20+
if ($entry -eq '-IcingaForWindowsRemoteExecution' -Or $entry -eq '-IcingaForWindowsJEARemoteExecution') {
21+
continue;
22+
}
23+
$ListArgs += $entry;
24+
}
25+
26+
if ($ExErrorId -Like "*ParameterArgumentTransformationError*" -And $ExMsg.Contains('System.Security.SecureString')) {
27+
$ExMsg = [string]::Format(
28+
'Cannot bind parameter {0}. Cannot convert the provided value for argument "{0}" of type "System.String" to type "System.Security.SecureString".',
29+
$ArgName
30+
);
31+
32+
$Arguments.Clear();
33+
$ListArgs = 'Hidden for security reasons';
34+
}
35+
36+
Write-IcingaConsolePlain '[UNKNOWN] Icinga Exception: {0}{1}{1}CheckCommand: {2}{1}Arguments: {3}{1}{1}StackTrace:{1}{4}' -Objects $ExMsg, (New-IcingaNewLine), $Command, $ListArgs, $StackTrace;
37+
$Global:Icinga.Private.Scheduler.ExitCode = 3;
38+
}

0 commit comments

Comments
 (0)