mirror of
https://github.com/Icinga/icinga-powershell-framework.git
synced 2025-12-20 23:00:35 -05:00
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.
This commit is contained in:
commit
694c31c7fc
4 changed files with 138 additions and 25 deletions
|
|
@ -47,6 +47,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
|
|||
* [#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`
|
||||
* [#448](https://github.com/Icinga/icinga-powershell-framework/pull/448) Adds support to sort arrays without ScriptBlocks
|
||||
* [#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
|
||||
* [#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)
|
||||
|
||||
## 1.7.1 (2021-11-11)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,31 +2,32 @@ function Invoke-IcingaInternalServiceCall()
|
|||
{
|
||||
param (
|
||||
[string]$Command = '',
|
||||
[array]$Arguments = @()
|
||||
[array]$Arguments = @(),
|
||||
[switch]$NoExit = $FALSE
|
||||
);
|
||||
|
||||
# If our Framework is running as daemon, never call our api
|
||||
if ($Global:Icinga.Protected.RunAsDaemon) {
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
# If the API forward feature is disabled, do nothing
|
||||
if ((Get-IcingaFrameworkApiChecks) -eq $FALSE) {
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
# Test our Icinga for Windows service. If the service is not installed or not running, execute the plugin locally
|
||||
$IcingaForWindowsService = (Get-Service 'icingapowershell' -ErrorAction SilentlyContinue);
|
||||
|
||||
if ($null -eq $IcingaForWindowsService -Or $IcingaForWindowsService.Status -ne 'Running') {
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
# In case the REST-Api module ist not configured, do nothing
|
||||
$BackgroundDaemons = Get-IcingaBackgroundDaemons;
|
||||
|
||||
if ($null -eq $BackgroundDaemons -Or $BackgroundDaemons.ContainsKey('Start-IcingaWindowsRESTApi') -eq $FALSE) {
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
$RestApiPort = 5668;
|
||||
|
|
@ -57,6 +58,11 @@ function Invoke-IcingaInternalServiceCall()
|
|||
[string]$Argument = [string]$Value;
|
||||
$ArgumentValue = $null;
|
||||
|
||||
if ($Argument -eq '-IcingaForWindowsRemoteExecution' -Or $Argument -eq '-IcingaForWindowsJEARemoteExecution') {
|
||||
$ArgumentIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($Value[0] -eq '-') {
|
||||
if (($ArgumentIndex + 1) -lt $Arguments.Count) {
|
||||
[string]$NextValue = $Arguments[$ArgumentIndex + 1];
|
||||
|
|
@ -85,7 +91,7 @@ function Invoke-IcingaInternalServiceCall()
|
|||
} catch {
|
||||
# Fallback to execute plugin locally
|
||||
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -ExceptionObject $_ -Objects $Command, $CommandArguments;
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
# Resolve our result from the API
|
||||
|
|
@ -95,12 +101,12 @@ function Invoke-IcingaInternalServiceCall()
|
|||
# In case we didn't receive a check result, fallback to local execution
|
||||
if ([string]::IsNullOrEmpty($IcingaResult.$Command.checkresult)) {
|
||||
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects 'The check result for the executed command was empty', $Command, $CommandArguments;
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($IcingaResult.$Command.exitcode)) {
|
||||
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects 'The check result for the executed command was empty', $Command, $CommandArguments;
|
||||
return;
|
||||
return $NULL;
|
||||
}
|
||||
|
||||
$IcingaCR = ($IcingaResult.$Command.checkresult.Replace("`r`n", "`n"));
|
||||
|
|
@ -112,6 +118,12 @@ function Invoke-IcingaInternalServiceCall()
|
|||
}
|
||||
}
|
||||
|
||||
if ($NoExit) {
|
||||
Set-IcingaInternalPluginExitCode -ExitCode $IcingaResult.$Command.exitcode;
|
||||
|
||||
return $IcingaCR;
|
||||
}
|
||||
|
||||
# Print our response and exit with the provide exit code
|
||||
Write-IcingaConsolePlain $IcingaCR;
|
||||
exit $IcingaResult.$Command.exitcode;
|
||||
|
|
|
|||
|
|
@ -4,34 +4,103 @@ function Exit-IcingaExecutePlugin()
|
|||
[string]$Command = ''
|
||||
);
|
||||
|
||||
$JEAProfile = Get-IcingaJEAContext;
|
||||
[string]$JEAProfile = Get-IcingaJEAContext;
|
||||
[bool]$CheckByIcingaForWindows = $FALSE;
|
||||
[bool]$CheckByJEAShell = $FALSE;
|
||||
|
||||
if ($args -Contains '-IcingaForWindowsRemoteExecution') {
|
||||
$CheckByIcingaForWindows = $TRUE;
|
||||
}
|
||||
if ($args -Contains '-IcingaForWindowsJEARemoteExecution') {
|
||||
$CheckByJEAShell = $TRUE;
|
||||
}
|
||||
|
||||
# We use the plugin check_by_icingaforwindows.ps1 to execute
|
||||
# checks from a Linux/Windows remote source
|
||||
if ($CheckByIcingaForWindows) {
|
||||
# First try to queue the check over the REST-Api
|
||||
$CheckResult = Invoke-IcingaInternalServiceCall -Command $Command -Arguments $args -NoExit;
|
||||
|
||||
if ($null -ne $CheckResult) {
|
||||
# Seems we got a result
|
||||
Write-IcingaConsolePlain -Message $CheckResult;
|
||||
|
||||
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
|
||||
# The plugin itself will terminate the session
|
||||
return;
|
||||
}
|
||||
|
||||
# We couldn't use our Rest-Api and Api-Checks feature, then lets execute the plugin locally
|
||||
# Set daemon true, because this will change internal handling for errors and plugin output
|
||||
$Global:Icinga.Protected.RunAsDaemon = $TRUE;
|
||||
|
||||
try {
|
||||
# Execute our plugin
|
||||
(& $Command @args) | Out-Null;
|
||||
} catch {
|
||||
# Handle errors within our plugins
|
||||
# If anything goes wrong handle the error very detailed
|
||||
|
||||
$Global:Icinga.Protected.RunAsDaemon = $FALSE;
|
||||
Write-IcingaExecutePluginException -Command $Command -ErrorObject $_ -Arguments $args;
|
||||
$args.Clear();
|
||||
|
||||
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
|
||||
# The plugin itself will terminate the session
|
||||
return;
|
||||
}
|
||||
|
||||
# Disable it again - we need to write data to our shell now. Not very intuitive, but it is the easiest
|
||||
# solution to do it this way
|
||||
$Global:Icinga.Protected.RunAsDaemon = $FALSE;
|
||||
|
||||
# Now print the result to shell
|
||||
Write-IcingaPluginResult -PluginOutput (Get-IcingaInternalPluginOutput) -PluginPerfData (Get-IcingaCheckSchedulerPerfData);
|
||||
|
||||
# Do not close the session, we need to read the ExitCode from Get-IcingaInternalPluginExitCode
|
||||
# The plugin itself will terminate the session
|
||||
return;
|
||||
}
|
||||
|
||||
# Regardless of JEA enabled or disabled, forward all checks to the internal API
|
||||
# and check if we get a result from there
|
||||
Invoke-IcingaInternalServiceCall -Command $Command -Arguments $args;
|
||||
|
||||
try {
|
||||
# If the plugin is not installed, throw a good exception
|
||||
Exit-IcingaPluginNotInstalled -Command $Command;
|
||||
|
||||
# In case we have JEA enabled on our system, this shell currently open most likely has no
|
||||
# JEA configuration installed. This is because a JEA shell will not return an exit code and
|
||||
# Icinga relies on that. Therefor we will try to open a new PowerShell with the JEA configuration
|
||||
# assigned for Icinga for Windows, execute the plugins there and return the result
|
||||
if ([string]::IsNullOrEmpty($JEAProfile) -eq $FALSE) {
|
||||
$ErrorHandler = ''
|
||||
$JEARun = (
|
||||
& powershell.exe -ConfigurationName $JEAProfile -NoLogo -NoProfile -Command {
|
||||
# Load Icinga for Windows
|
||||
Use-Icinga;
|
||||
|
||||
# Enable our JEA context
|
||||
$Global:Icinga.Protected.JEAContext = $TRUE;
|
||||
|
||||
# Parse the arguments our previous shell received
|
||||
$Command = $args[0];
|
||||
$Arguments = $args[1];
|
||||
$Output = '';
|
||||
|
||||
try {
|
||||
# Try executing our checks, store the exit code and plugin output
|
||||
$ExitCode = (& $Command @Arguments);
|
||||
$Output = (Get-IcingaInternalPluginOutput);
|
||||
$ExitCode = (Get-IcingaInternalPluginExitCode);
|
||||
} catch {
|
||||
# If we failed for some reason, print a detailed error and use exit code 3 to mark the check as unkown
|
||||
$Output = [string]::Format('[UNKNOWN] Icinga Exception: Error while executing plugin in JEA context{0}{0}{1}', (New-IcingaNewLine), $_.Exception.Message);
|
||||
$ExitCode = 3;
|
||||
}
|
||||
|
||||
# Return the result to our main PowerShell
|
||||
return @{
|
||||
'Output' = $Output;
|
||||
'PerfData' = (Get-IcingaCheckSchedulerPerfData)
|
||||
|
|
@ -40,34 +109,27 @@ function Exit-IcingaExecutePlugin()
|
|||
} -args $Command, $args
|
||||
) 2>$ErrorHandler;
|
||||
|
||||
# If we have an exit code larger or equal 0, the execution inside the JEA shell was successfully and we can share the result
|
||||
# In case we had an error inside the JEA shell, it will returned here as well
|
||||
if ($LASTEXITCODE -ge 0) {
|
||||
Write-IcingaPluginResult -PluginOutput $JEARun.Output -PluginPerfData $JEARun.PerfData;
|
||||
exit $JEARun.ExitCode;
|
||||
} else {
|
||||
# If for some reason the PowerShell could not be started within JEA context, we can throw an exception with exit code 3
|
||||
# to mark the check as unknown including our error message
|
||||
Write-IcingaConsolePlain '[UNKNOWN] Icinga Exception: Unable to start the PowerShell.exe with the provided JEA profile "{0}" for CheckCommand: {1}' -Objects $JEAProfile, $Command;
|
||||
exit 3;
|
||||
}
|
||||
} else {
|
||||
# If we simply run the check without JEA context or from remote, we can just execute the plugin and
|
||||
# exit with the exit code received from the result
|
||||
exit (& $Command @args);
|
||||
}
|
||||
} catch {
|
||||
$ExMsg = $_.Exception.Message;
|
||||
$StackTrace = $_.ScriptStackTrace;
|
||||
$ExErrorId = $_.FullyQualifiedErrorId;
|
||||
$ArgName = $_.Exception.ParameterName;
|
||||
$ListArgs = $args;
|
||||
# If anything goes wrong handle the error
|
||||
Write-IcingaExecutePluginException -Command $Command -ErrorObject $_ -Arguments $args;
|
||||
$args.Clear();
|
||||
|
||||
if ($ExErrorId -Like "*ParameterArgumentTransformationError*" -And $ExMsg.Contains('System.Security.SecureString')) {
|
||||
$ExMsg = [string]::Format(
|
||||
'Cannot bind parameter {0}. Cannot convert the provided value for argument "{0}" of type "System.String" to type "System.Security.SecureString".',
|
||||
$ArgName
|
||||
);
|
||||
|
||||
$args.Clear();
|
||||
$ListArgs = 'Hidden for security reasons';
|
||||
}
|
||||
|
||||
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;
|
||||
exit 3;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
lib/icinga/plugin/Write-IcingaExecutePluginException.psm1
Normal file
38
lib/icinga/plugin/Write-IcingaExecutePluginException.psm1
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
function Write-IcingaExecutePluginException()
|
||||
{
|
||||
param (
|
||||
$Command = '',
|
||||
$ErrorObject = $null,
|
||||
$Arguments = @()
|
||||
);
|
||||
|
||||
if ($null -eq $ErrorObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ExMsg = $ErrorObject.Exception.Message;
|
||||
$StackTrace = $ErrorObject.ScriptStackTrace;
|
||||
$ExErrorId = $ErrorObject.FullyQualifiedErrorId;
|
||||
$ArgName = $ErrorObject.Exception.ParameterName;
|
||||
$ListArgs = @();
|
||||
|
||||
foreach ($entry in $Arguments) {
|
||||
if ($entry -eq '-IcingaForWindowsRemoteExecution' -Or $entry -eq '-IcingaForWindowsJEARemoteExecution') {
|
||||
continue;
|
||||
}
|
||||
$ListArgs += $entry;
|
||||
}
|
||||
|
||||
if ($ExErrorId -Like "*ParameterArgumentTransformationError*" -And $ExMsg.Contains('System.Security.SecureString')) {
|
||||
$ExMsg = [string]::Format(
|
||||
'Cannot bind parameter {0}. Cannot convert the provided value for argument "{0}" of type "System.String" to type "System.Security.SecureString".',
|
||||
$ArgName
|
||||
);
|
||||
|
||||
$Arguments.Clear();
|
||||
$ListArgs = 'Hidden for security reasons';
|
||||
}
|
||||
|
||||
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;
|
||||
$Global:Icinga.Private.Scheduler.ExitCode = 3;
|
||||
}
|
||||
Loading…
Reference in a new issue