mirror of
https://github.com/Icinga/icinga-powershell-framework.git
synced 2025-12-21 07:10:15 -05:00
Adds Rest-Api and Api-Checks into framework
This commit is contained in:
parent
fedc34bbb2
commit
253cb7f4dd
19 changed files with 1032 additions and 0 deletions
|
|
@ -4,6 +4,19 @@ Upgrading Icinga PowerShell Framework is usually quite straightforward.
|
||||||
|
|
||||||
Specific version upgrades are described below. Please note that version updates are incremental.
|
Specific version upgrades are described below. Please note that version updates are incremental.
|
||||||
|
|
||||||
|
## Upgrading to v1.7.0 (2021-11-09)
|
||||||
|
|
||||||
|
### REST-Api and Api-Checks
|
||||||
|
|
||||||
|
With Icinga for Windows v1.7.0, the previously separate available components REST-Api [icinga-powershell-restapi](https://icinga.com/docs/icinga-for-windows/latest/restapi/doc/01-Introduction/) and API-Checks [icinga-powershell-apichecks](https://icinga.com/docs/icinga-for-windows/latest/apichecks/doc/01-Introduction/) are now directly baked into the Icinga PowerShell Framework. You will no longer require to install these components in addition.
|
||||||
|
|
||||||
|
**Upgrading**: If you previously installed these components, you should remove them from the system before actively using Icinga for Windows v1.7.0, as additional changes were made in this case.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Uninstall-IcingaComponent -Name 'restapi';
|
||||||
|
Uninstall-IcingaComponent -Name 'apichecks';
|
||||||
|
```
|
||||||
|
|
||||||
## Upgrading to v1.5.0 (2021-06-02)
|
## Upgrading to v1.5.0 (2021-06-02)
|
||||||
|
|
||||||
### `SecureString` and Icinga Director Baskets
|
### `SecureString` and Icinga Director Baskets
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
|
||||||
* [#376](https://github.com/Icinga/icinga-powershell-framework/pull/376) Fixes IMC error handling on invalid JSON for installation command/file
|
* [#376](https://github.com/Icinga/icinga-powershell-framework/pull/376) Fixes IMC error handling on invalid JSON for installation command/file
|
||||||
* [#381](https://github.com/Icinga/icinga-powershell-framework/issues/381) Fixes Repository Hash generator for new repositories, which always returned the same hash regardless of the files inside
|
* [#381](https://github.com/Icinga/icinga-powershell-framework/issues/381) Fixes Repository Hash generator for new repositories, which always returned the same hash regardless of the files inside
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#383](https://github.com/Icinga/icinga-powershell-framework/pull/383) Moves the components REST-Api [icinga-powershell-restapi](https://icinga.com/docs/icinga-for-windows/latest/restapi/doc/01-Introduction/) and API-Checks [icinga-powershell-apichecks](https://icinga.com/docs/icinga-for-windows/latest/apichecks/doc/01-Introduction/) directly into the Framework
|
||||||
|
|
||||||
## 1.6.1 (2021-09-15)
|
## 1.6.1 (2021-09-15)
|
||||||
|
|
||||||
[Issue and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/21?closed=1)
|
[Issue and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/21?closed=1)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
function Add-IcingaRESTClientBlacklistCount()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[System.Net.Sockets.TcpClient]$Client = $null,
|
||||||
|
$ClientList = $null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($null -eq $Client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
|
||||||
|
[string]$IpAddress = $Endpoint.Split(':')[0];
|
||||||
|
[int]$Value = Get-IcingaHashtableItem `
|
||||||
|
-Hashtable $ClientList `
|
||||||
|
-Key $IpAddress `
|
||||||
|
-NullValue 0;
|
||||||
|
|
||||||
|
Add-IcingaHashtableItem `
|
||||||
|
-Hashtable $ClientList `
|
||||||
|
-Key $IpAddress `
|
||||||
|
-Value ($Value + 1) `
|
||||||
|
-Override | Out-Null;
|
||||||
|
}
|
||||||
55
lib/daemons/RestAPI/client/Invoke-IcingaRESTAPIv1Calls.psm1
Normal file
55
lib/daemons/RestAPI/client/Invoke-IcingaRESTAPIv1Calls.psm1
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
function Invoke-IcingaRESTAPIv1Calls()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[Hashtable]$Request = @{},
|
||||||
|
[Hashtable]$Connection = @{}
|
||||||
|
);
|
||||||
|
|
||||||
|
[string]$ModuleToLoad = Get-IcingaRESTPathElement -Request $Request -Index 1;
|
||||||
|
# Map our Icinga globals to a shorter variable
|
||||||
|
$RestDaemon = $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi;
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($ModuleToLoad)) {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.Ok) `
|
||||||
|
-ContentBody @{
|
||||||
|
'Endpoints' = @(
|
||||||
|
$RestDaemon.RegisteredEndpoints.Keys
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($RestDaemon.RegisteredEndpoints.ContainsKey($ModuleToLoad) -eq $FALSE) {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Not Found') `
|
||||||
|
-ContentBody 'There was no module found which is registered for this endpoint name.'
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[string]$Command = $RestDaemon.RegisteredEndpoints[$ModuleToLoad];
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage -Message 'Executing REST-Module' -Objects $Command;
|
||||||
|
|
||||||
|
if ($null -eq (Get-Command $Command -ErrorAction SilentlyContinue)) {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Internal Server Error') `
|
||||||
|
-ContentBody 'This API endpoint is registered, but the PowerShell Cmdlet this module is referencing too does not exist. Please check if the module was installed correctly and contact the developer if you require assistance to resolve this issue.'
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hashtable]$CommandArguments = @{
|
||||||
|
'-Request' = $Request;
|
||||||
|
'-Connection' = $Connection;
|
||||||
|
'-IcingaGlobals' = $IcingaDaemonData;
|
||||||
|
'-ApiVersion' = 'v1';
|
||||||
|
};
|
||||||
|
|
||||||
|
& $Command @CommandArguments | Out-Null;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
function Remove-IcingaRESTClientBlacklist()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[System.Net.Sockets.TcpClient]$Client = $null,
|
||||||
|
$ClientList = $null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($null -eq $Client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
|
||||||
|
[string]$IpAddress = $Endpoint.Split(':')[0];
|
||||||
|
|
||||||
|
Remove-IcingaHashtableItem `
|
||||||
|
-Hashtable $ClientList `
|
||||||
|
-Key $IpAddress;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
function Test-IcingaRESTClientBlacklisted()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[System.Net.Sockets.TcpClient]$Client = $null,
|
||||||
|
$ClientList = $null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($null -eq $Client) {
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
|
||||||
|
[string]$IpAddress = $Endpoint.Split(':')[0];
|
||||||
|
[int]$Value = Get-IcingaHashtableItem `
|
||||||
|
-Hashtable $ClientList `
|
||||||
|
-Key $IpAddress `
|
||||||
|
-NullValue 0;
|
||||||
|
|
||||||
|
# After 9 invalid attempts we will blacklist the client
|
||||||
|
if ($Value -gt 9) {
|
||||||
|
Write-IcingaDebugMessage -Message 'Client is blacklisted' -Objects $IpAddress, $Value, $Client.Client;
|
||||||
|
return $TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
function Test-IcingaRESTClientConnection()
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
[Hashtable]$Connection = @{}
|
||||||
|
);
|
||||||
|
|
||||||
|
# If we couldn't establish a proper SSL stream, close the connection
|
||||||
|
# immediately without opening a client connection thread
|
||||||
|
if ($null -eq $Connection.Stream) {
|
||||||
|
Add-IcingaRESTClientBlacklistCount `
|
||||||
|
-Client $Connection.Client `
|
||||||
|
-ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
|
||||||
|
Write-IcingaEventMessage -EventId 1501 -Namespace 'Framework' -Objects $Connection.Client.Client;
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
$Connection = $null;
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage 'Client connection has passed Test';
|
||||||
|
|
||||||
|
return $TRUE;
|
||||||
|
}
|
||||||
164
lib/daemons/RestAPI/daemon/New-IcingaForWindowsRESTApi.psm1
Normal file
164
lib/daemons/RestAPI/daemon/New-IcingaForWindowsRESTApi.psm1
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
function New-IcingaForWindowsRESTApi()
|
||||||
|
{
|
||||||
|
# Allow us to parse the framework global data to this thread
|
||||||
|
param (
|
||||||
|
$IcingaDaemonData,
|
||||||
|
$Port,
|
||||||
|
$RootFolder,
|
||||||
|
$CertFile,
|
||||||
|
$CertThumbprint,
|
||||||
|
$RequireAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
# Import the framework library components and initialise it
|
||||||
|
# as daemon
|
||||||
|
Use-Icinga -LibOnly -Daemon;
|
||||||
|
|
||||||
|
$Global:IcingaDaemonData = $IcingaDaemonData;
|
||||||
|
|
||||||
|
# Add a synchronized hashtable to the global data background
|
||||||
|
# daemon hashtable to write data to. In addition it will
|
||||||
|
# allow to share data collected from this daemon with others
|
||||||
|
$IcingaDaemonData.BackgroundDaemon.Add(
|
||||||
|
'IcingaPowerShellRestApi',
|
||||||
|
[hashtable]::Synchronized(@{})
|
||||||
|
);
|
||||||
|
|
||||||
|
# Map our Icinga globals to a shorter variable
|
||||||
|
$RestDaemon = $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi;
|
||||||
|
|
||||||
|
# This will add another hashtable to our previous
|
||||||
|
# IcingaPowerShellRestApi hashtable to store actual
|
||||||
|
# endpoint configurations for the API
|
||||||
|
$RestDaemon.Add(
|
||||||
|
'RegisteredEndpoints',
|
||||||
|
[hashtable]::Synchronized(@{})
|
||||||
|
);
|
||||||
|
|
||||||
|
# This will add another hashtable to our previous
|
||||||
|
# IcingaPowerShellRestApi hashtable to store actual
|
||||||
|
# command aliases for execution for the API
|
||||||
|
$RestDaemon.Add(
|
||||||
|
'CommandAliases',
|
||||||
|
[hashtable]::Synchronized(@{})
|
||||||
|
);
|
||||||
|
|
||||||
|
# This will add another hashtable to our previous
|
||||||
|
# IcingaPowerShellRestApi hashtable to store actual
|
||||||
|
# command aliases for execution for the API
|
||||||
|
$RestDaemon.Add(
|
||||||
|
'ClientBlacklist',
|
||||||
|
[hashtable]::Synchronized(@{})
|
||||||
|
);
|
||||||
|
|
||||||
|
# Make the root folder of our rest daemon module available
|
||||||
|
# for every possible thread we require
|
||||||
|
$RestDaemon.Add(
|
||||||
|
'RootFolder', $RootFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
$RESTEndpoints = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTAPIEndpoint*';
|
||||||
|
Write-IcingaDebugMessage -Message (
|
||||||
|
[string]::Format(
|
||||||
|
'Loading configuration for REST-Endpoints{0}{1}',
|
||||||
|
(New-IcingaNewLine),
|
||||||
|
($RESTEndpoints | Out-String)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage -Message ($RestDaemon | Out-String);
|
||||||
|
|
||||||
|
foreach ($entry in $RESTEndpoints.Values) {
|
||||||
|
[bool]$Success = Add-IcingaHashtableItem -Hashtable $RestDaemon.RegisteredEndpoints `
|
||||||
|
-Key $entry.Alias `
|
||||||
|
-Value $entry.Command;
|
||||||
|
|
||||||
|
if ($Success -eq $FALSE) {
|
||||||
|
Write-IcingaEventMessage `
|
||||||
|
-EventId 2100 `
|
||||||
|
-Namespace 'RESTApi' `
|
||||||
|
-Objects ([string]::Format('Adding duplicated REST endpoint "{0}" with command "{1}', $entry.Alias, $entry.Command)), $RESTEndpoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$CommandAliases = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTApiCommandAliases*';
|
||||||
|
|
||||||
|
foreach ($entry in $CommandAliases.Values) {
|
||||||
|
foreach ($component in $entry.Keys) {
|
||||||
|
[bool]$Success = Add-IcingaHashtableItem -Hashtable $RestDaemon.CommandAliases `
|
||||||
|
-Key $component `
|
||||||
|
-Value $entry[$component];
|
||||||
|
|
||||||
|
if ($Success -eq $FALSE) {
|
||||||
|
Write-IcingaEventMessage `
|
||||||
|
-EventId 2101 `
|
||||||
|
-Namespace 'RESTApi' `
|
||||||
|
-Objects ([string]::Format('Adding duplicated REST command aliases "{0}" for namespace "{1}', $entry[$component], $component)), $CommandAliases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage -Message ($RestDaemon.RegisteredEndpoints | Out-String);
|
||||||
|
|
||||||
|
if ($Global:IcingaDaemonData.JEAContext) {
|
||||||
|
if ($global:IcingaDaemonData.ContainsKey('SSLCertificate') -eq $FALSE -Or $null -eq $global:IcingaDaemonData.SSLCertificate) {
|
||||||
|
Write-IcingaEventMessage -EventId 2001 -Namespace 'RESTApi';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Certificate = $global:IcingaDaemonData.SSLCertificate;
|
||||||
|
} else {
|
||||||
|
$Certificate = Get-IcingaSSLCertForSocket -CertFile $CertFile -CertThumbprint $CertThumbprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $Certificate) {
|
||||||
|
Write-IcingaEventMessage -EventId 2000 -Namespace 'RESTApi';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Socket = New-IcingaTCPSocket -Address $Address -Port $Port -Start;
|
||||||
|
|
||||||
|
# Keep our code executed as long as the PowerShell service is
|
||||||
|
# being executed. This is required to ensure we will execute
|
||||||
|
# the code frequently instead of only once
|
||||||
|
while ($TRUE) {
|
||||||
|
$Connection = Open-IcingaTCPClientConnection `
|
||||||
|
-Client (New-IcingaTCPClient -Socket $Socket) `
|
||||||
|
-Certificate $Certificate;
|
||||||
|
|
||||||
|
if (Test-IcingaRESTClientBlacklisted -Client $Connection.Client -ClientList $RestDaemon.ClientBlacklist) {
|
||||||
|
Write-IcingaDebugMessage -Message 'A remote client which is trying to connect was blacklisted' -Objects $Connection.Client.Client;
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Test-IcingaRESTClientConnection -Connection $Connection) -eq $FALSE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API not yet ready
|
||||||
|
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.Count -eq 0) {
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$NextRESTApiThreadId = (Get-IcingaNextRESTApiThreadId);
|
||||||
|
|
||||||
|
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.ContainsKey($NextRESTApiThreadId) -eq $FALSE) {
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$NextRESTApiThreadId.Enqueue($Connection);
|
||||||
|
} catch {
|
||||||
|
$ExMsg = $_.Exception.Message;
|
||||||
|
Write-IcingaEventMessage -Namespace 'RESTApi' -EvenId 2050 -Objects $ExMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup the error stack and remove not required data
|
||||||
|
$Error.Clear();
|
||||||
|
# Force PowerShell to call the garbage collector to free memory
|
||||||
|
[System.GC]::Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
93
lib/daemons/RestAPI/daemon/Start-IcingaWindowsRESTApi.psm1
Normal file
93
lib/daemons/RestAPI/daemon/Start-IcingaWindowsRESTApi.psm1
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
A background daemon for Icinga for Windows, providing a REST-Api interface over secure
|
||||||
|
TLS (https) connections. As certificates either custom ones are used or by default the
|
||||||
|
Icinga Agent certificates
|
||||||
|
.DESCRIPTION
|
||||||
|
This background daemon provides a REST-Api interface by default on Port 5668 over
|
||||||
|
TLS connections. No unencrypted http requests are allowed. As certificate it will
|
||||||
|
either use the default Icinga Agent certificates (default config) or custom generated
|
||||||
|
ones, specified as arguments while installing the daemon.
|
||||||
|
|
||||||
|
In addition you can enable authentication, which allows local users or domain users.
|
||||||
|
|
||||||
|
By default 5 concurrent threads will perform the work on API requests, can how ever be
|
||||||
|
reduced or increased depending on configuration.
|
||||||
|
|
||||||
|
More Information on
|
||||||
|
https://icinga.com/docs/icinga-for-windows/latest/restapi
|
||||||
|
.PARAMETER Address
|
||||||
|
Allows to specify on which Address a socket should be created on. Defaults to loopback if empty
|
||||||
|
.PARAMETER Port
|
||||||
|
The Port the REST-Api will listen on. Defaults to 5668
|
||||||
|
.PARAMETER CertFile
|
||||||
|
Use this to define a path on your local disk of you are using custom certificates.
|
||||||
|
Supported files are: .pfx, .crt
|
||||||
|
|
||||||
|
By default, the local Icinga Agent certificates are used
|
||||||
|
.PARAMETER CertThumbprint
|
||||||
|
Provide a thumbprint of a certificate stored within the local Windows Cert store
|
||||||
|
|
||||||
|
By default, the local Icinga Agent certificates are used
|
||||||
|
.PARAMETER RequireAuth
|
||||||
|
Enable authentication which will add basic auth prompt for any request on the API.
|
||||||
|
For authentication you can either use local Windows accounts or domain accounts
|
||||||
|
.PARAMETER ConcurrentThreads
|
||||||
|
Defines on how many threads are started to process API requests. Defaults to 5
|
||||||
|
.PARAMETER Timeout
|
||||||
|
Not for use on this module directly, but allows other modules and features to properly
|
||||||
|
get an idea after which time interval connections are terminated
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-restapi
|
||||||
|
.NOTES
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Start-IcingaWindowsRESTApi()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[string]$Address = '',
|
||||||
|
[int]$Port = 5668,
|
||||||
|
[string]$CertFile = $null,
|
||||||
|
[string]$CertThumbprint = $null,
|
||||||
|
[bool]$RequireAuth = $FALSE,
|
||||||
|
[int]$ConcurrentThreads = 5,
|
||||||
|
[int]$Timeout = 30
|
||||||
|
);
|
||||||
|
|
||||||
|
$RootFolder = $PSScriptRoot;
|
||||||
|
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.Add('RESTApi', ([hashtable]::Synchronized(@{})));
|
||||||
|
$global:IcingaDaemonData.IcingaThreadPool.Add('IcingaRESTApi', (New-IcingaThreadPool -MaxInstances ($ThreadId + 3)));
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.RESTApi.Add('ApiRequests', ([hashtable]::Synchronized(@{})));
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.RESTApi.Add('ApiCallThreadAssignment', ([hashtable]::Synchronized(@{})));
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.RESTApi.Add('TotalThreads', $ConcurrentThreads);
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.RESTApi.Add('LastThreadId', 0);
|
||||||
|
|
||||||
|
# Now create a new thread for our REST-Api, assign a name and parse all required arguments to it.
|
||||||
|
# Last but not least start it directly
|
||||||
|
New-IcingaThreadInstance `
|
||||||
|
-Name 'Icinga_for_Windows_REST_Api' `
|
||||||
|
-ThreadPool $global:IcingaDaemonData.IcingaThreadPool.IcingaRESTApi `
|
||||||
|
-Command 'New-IcingaForWindowsRESTApi' `
|
||||||
|
-CmdParameters @{
|
||||||
|
'IcingaDaemonData' = $global:IcingaDaemonData;
|
||||||
|
'Port' = $Port;
|
||||||
|
'RootFolder' = $RootFolder;
|
||||||
|
'CertFile' = $CertFile;
|
||||||
|
'CertThumbprint' = $CertThumbprint;
|
||||||
|
'RequireAuth' = $RequireAuth;
|
||||||
|
} `
|
||||||
|
-Start;
|
||||||
|
|
||||||
|
$ThreadId = 0;
|
||||||
|
|
||||||
|
while ($ConcurrentThreads -gt 0) {
|
||||||
|
$ConcurrentThreads = $ConcurrentThreads - 1;
|
||||||
|
[System.Collections.Queue]$RESTThreadQueue = @();
|
||||||
|
$global:IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.Add($ThreadId, [System.Collections.Queue]::Synchronized($RESTThreadQueue));
|
||||||
|
Start-IcingaForWindowsRESTThread -ThreadId $ThreadId -RequireAuth:$RequireAuth;
|
||||||
|
$ThreadId += 1;
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
function Register-IcingaEventLogMessagesRESTApi()
|
||||||
|
{
|
||||||
|
return @{
|
||||||
|
'RESTApi' = @{
|
||||||
|
2000 = @{
|
||||||
|
'EntryType' = 'Error';
|
||||||
|
'Message' = 'Failed to start REST-Api daemon, as no valid provided SSL and Icinga 2 Agent certificate was found';
|
||||||
|
'Details' = 'While starting the Icinga for Windows REST-Api daemon, no valid certificate was found for usage. You can either share a valid certificate by defining the full path with `-CertFile` to a .crt, .cert or .pfx file, by using `-CertThumbprint` to lookup a certificate inside the Microsoft cert store and by default the Icinga 2 Agent certificates. Please note that only Icinga 2 Agent version 2.8.0 or later are supported';
|
||||||
|
'EventId' = 2000;
|
||||||
|
};
|
||||||
|
2001 = @{
|
||||||
|
'EntryType' = 'Error';
|
||||||
|
'Message' = 'Failed to start REST-Api daemon in JEA context';
|
||||||
|
'Details' = 'Icinga for Windows is being used inside a JEA context as service with the REST-Api daemon. To establish a secure TLS socket, it is required to create certificates in advance for the socket to bind on with "Install-IcingaForWindowsCertificate". The REST-Api daemon will now exit.';
|
||||||
|
'EventId' = 2001;
|
||||||
|
};
|
||||||
|
2050 = @{
|
||||||
|
'EntryType' = 'Error';
|
||||||
|
'Message' = 'Failed to parse received REST-Api call';
|
||||||
|
'Details' = 'An API call send to the daemon could not be processed and caused an exception. Further details about the cause of this error can be found below.';
|
||||||
|
'EventId' = 2050;
|
||||||
|
};
|
||||||
|
2051 = @{
|
||||||
|
'EntryType' = 'Error';
|
||||||
|
'Message' = 'Failed to execute API call';
|
||||||
|
'Details' = 'An API call could not be processed due to an internal exception.';
|
||||||
|
'EventId' = 2051;
|
||||||
|
};
|
||||||
|
2052 = @{
|
||||||
|
'EntryType' = 'Error';
|
||||||
|
'Message' = 'Internal exception on calling API command';
|
||||||
|
'Details' = 'An internal command assigned to an API request could not be executed and caused an exception.';
|
||||||
|
'EventId' = 2052;
|
||||||
|
};
|
||||||
|
2100 = @{
|
||||||
|
'EntryType' = 'Warning';
|
||||||
|
'Message' = 'Failed to add namespace configuration for executed commands, as previous commands are reporting identical namespace identifiers';
|
||||||
|
'Details' = 'This warning occurs while the REST-Api is trying to auto-load different resources automatically to provide for example inventory information or any other auto-loaded configurations. Please review your installed modules, check the detailed description which modules and Cmdlets caused this conflict and either resolve it or get in contact with the corresponding developers.';
|
||||||
|
'EventId' = 2100;
|
||||||
|
};
|
||||||
|
2101 = @{
|
||||||
|
'EntryType' = 'Warning';
|
||||||
|
'Message' = 'Failed to add namespace configuration for command aliases, as an identical namespace was already added';
|
||||||
|
'Details' = 'This warning occurs while the REST-Api is trying to auto-load different resources automatically to provide for example command aliases for providing inventory information or any other auto-loaded configurations. Please review your installed modules, check the detailed description which modules and Cmdlets caused this conflict and either resolve it or get in contact with the corresponding developers.';
|
||||||
|
'EventId' = 2101;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
function Get-IcingaNextRESTApiThreadId()
|
||||||
|
{
|
||||||
|
[int]$ConcurrentThreads = $IcingaDaemonData.IcingaThreadContent.RESTApi.TotalThreads - 1;
|
||||||
|
[int]$LastThreadId = $IcingaDaemonData.IcingaThreadContent.RESTApi.LastThreadId + 1;
|
||||||
|
|
||||||
|
if ($LastThreadId -gt $ConcurrentThreads) {
|
||||||
|
$LastThreadId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$IcingaDaemonData.IcingaThreadContent.RESTApi.LastThreadId = $LastThreadId;
|
||||||
|
|
||||||
|
return $LastThreadId;
|
||||||
|
}
|
||||||
118
lib/daemons/RestAPI/threads/New-IcingaForWindowsRESTThread.psm1
Normal file
118
lib/daemons/RestAPI/threads/New-IcingaForWindowsRESTThread.psm1
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
function New-IcingaForWindowsRESTThread()
|
||||||
|
{
|
||||||
|
param(
|
||||||
|
$IcingaDaemonData,
|
||||||
|
$RequireAuth,
|
||||||
|
$ThreadId
|
||||||
|
);
|
||||||
|
|
||||||
|
# Import the framework library components and initialise it
|
||||||
|
# as daemon
|
||||||
|
Use-Icinga -LibOnly -Daemon;
|
||||||
|
$Global:IcingaDaemonData = $IcingaDaemonData;
|
||||||
|
|
||||||
|
# Initialise our performance counter categories
|
||||||
|
Show-IcingaPerformanceCounterCategories | Out-Null;
|
||||||
|
|
||||||
|
while ($TRUE) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.ContainsKey($ThreadId) -eq $FALSE) {
|
||||||
|
Start-Sleep -Milliseconds 10;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$ThreadId.Count -eq 0) {
|
||||||
|
Start-Sleep -Milliseconds 10;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Connection = $IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$ThreadId.Dequeue();
|
||||||
|
|
||||||
|
if ($null -eq $Connection) {
|
||||||
|
Start-Sleep -Milliseconds 10;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the received message from the stream by using our smart functions
|
||||||
|
[string]$RestMessage = Read-IcingaTCPStream -Client $Connection.Client -Stream $Connection.Stream;
|
||||||
|
# Now properly translate the entire rest message to a parseable hashtable
|
||||||
|
$RESTRequest = Read-IcingaRestMessage -RestMessage $RestMessage -Connection $Connection;
|
||||||
|
|
||||||
|
if ($null -ne $RESTRequest) {
|
||||||
|
|
||||||
|
# Check if we require to authenticate the user
|
||||||
|
if ($RequireAuth) {
|
||||||
|
# If no authentication header is provided we should show the prompt
|
||||||
|
if ([string]::IsNullOrEmpty($RESTRequest.Header.Authorization)) {
|
||||||
|
# In case we do not send an authentication header increase the blacklist counter
|
||||||
|
# to ensure we are not spammed and "attacked" by a client with useless requests
|
||||||
|
Add-IcingaRESTClientBlacklistCount `
|
||||||
|
-Client $Connection.Client `
|
||||||
|
-ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
|
||||||
|
# Send the authentication prompt
|
||||||
|
Send-IcingaWebAuthMessage -Connection $Connection;
|
||||||
|
# Close the connection
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Credentials = Convert-Base64ToCredentials -AuthString $RESTRequest.Header.Authorization;
|
||||||
|
[bool]$LoginSuccess = Test-IcingaRESTCredentials -UserName $Credentials.user -Password $Credentials.password -Domain $Credentials.domain;
|
||||||
|
$Credentials = $null;
|
||||||
|
|
||||||
|
# Handle login failures
|
||||||
|
if ($LoginSuccess -eq $FALSE) {
|
||||||
|
# Failed attempts should increase the blacklist counter
|
||||||
|
Add-IcingaRESTClientBlacklistCount `
|
||||||
|
-Client $Connection.Client `
|
||||||
|
-ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
|
||||||
|
# Re-send the authentication prompt
|
||||||
|
Send-IcingaWebAuthMessage -Connection $Connection;
|
||||||
|
# Close the connection
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# We should remove clients from the blacklist who are sending valid requests
|
||||||
|
Remove-IcingaRESTClientBlacklist -Client $Connection.Client -ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
|
||||||
|
switch (Get-IcingaRESTPathElement -Request $RESTRequest -Index 0) {
|
||||||
|
'v1' {
|
||||||
|
Invoke-IcingaRESTAPIv1Calls -Request $RESTRequest -Connection $Connection;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
Write-IcingaDebugMessage -Message ('Invalid API call - no version specified' + ($RESTRequest.RequestPath | Out-String));
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Not Found') `
|
||||||
|
-ContentBody 'Invalid API call received. No version specified.'
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$ExMsg = $_.Exception.Message;
|
||||||
|
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Internal Server Error') `
|
||||||
|
-ContentBody $ExMsg
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
|
||||||
|
Write-IcingaEventMessage -Namespace 'RESTApi' -EventId 2051 -Objects $ExMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Finally close the clients connection as we are done here and
|
||||||
|
# ensure this thread will close by simply leaving the function
|
||||||
|
if ($null -ne $Connection) {
|
||||||
|
Close-IcingaTCPConnection -Client $Connection.Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup the error stack and remove not required data
|
||||||
|
$Error.Clear();
|
||||||
|
# Force PowerShell to call the garbage collector to free memory
|
||||||
|
[System.GC]::Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
function Start-IcingaForWindowsRESTThread()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[int]$ThreadId = 0,
|
||||||
|
[switch]$RequireAuth = $FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
# Now create a new thread, assign a name and parse all required arguments to it.
|
||||||
|
# Last but not least start it directly
|
||||||
|
New-IcingaThreadInstance `
|
||||||
|
-Name ([string]::Format("Icinga_Windows_REST_Api_Thread_{0}", $ThreadId)) `
|
||||||
|
-ThreadPool (New-IcingaThreadPool -MaxInstances 1) `
|
||||||
|
-Command 'New-IcingaForWindowsRESTThread' `
|
||||||
|
-CmdParameters @{
|
||||||
|
'IcingaDaemonData' = $global:IcingaDaemonData;
|
||||||
|
'RequireAuth' = $RequireAuth;
|
||||||
|
'ThreadId' = $ThreadId;
|
||||||
|
} `
|
||||||
|
-Start;
|
||||||
|
}
|
||||||
53
lib/daemons/RestAPI/tools/Add-IcingaRESTApiCommand.psm1
Normal file
53
lib/daemons/RestAPI/tools/Add-IcingaRESTApiCommand.psm1
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Adds a Cmdlet to an REST-Api endpoint which is either whitelisted or blacklisted.
|
||||||
|
Whitelisted Cmdlets can be executed over API endpoints, blacklisted not.
|
||||||
|
Use '*' for wildcard matches
|
||||||
|
.DESCRIPTION
|
||||||
|
Adds a Cmdlet to an REST-Api endpoint which is either whitelisted or blacklisted.
|
||||||
|
Whitelisted Cmdlets can be executed over API endpoints, blacklisted not.
|
||||||
|
Use '*' for wildcard matches
|
||||||
|
.FUNCTIONALITY
|
||||||
|
Enables or disables Cmdlets for REST-Api endpoints
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Add-IcingaRESTApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker';
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Add-IcingaRESTApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker' -Blacklist;
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-restapi
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Add-IcingaRESTApiCommand()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[string]$Command = '',
|
||||||
|
[string]$Endpoint = '',
|
||||||
|
[switch]$Blacklist = $FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($Command) -Or [string]::IsNullOrEmpty($Endpoint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Commands = $null;
|
||||||
|
$ConfigPath = ([string]::Format('RESTApi.Commands.{0}.Whitelist', $Endpoint));
|
||||||
|
[array]$Values = @();
|
||||||
|
|
||||||
|
if ($Blacklist) {
|
||||||
|
$ConfigPath = ([string]::Format('RESTApi.Commands.{0}.Blacklist', $Endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
$Commands = Get-IcingaPowerShellConfig -Path $ConfigPath;
|
||||||
|
|
||||||
|
if ((Test-IcingaPowerShellConfigItem -ConfigObject $Commands -ConfigKey $Command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $Commands) {
|
||||||
|
$Values = $Commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Values += $Command;
|
||||||
|
|
||||||
|
Set-IcingaPowerShellConfig -Path $ConfigPath -Value $Values;
|
||||||
|
}
|
||||||
49
lib/daemons/RestAPI/tools/Remove-IcingaRESTApiCommand.psm1
Normal file
49
lib/daemons/RestAPI/tools/Remove-IcingaRESTApiCommand.psm1
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes a Cmdlet from an REST-Api endpoints whitelist or blacklist.
|
||||||
|
.DESCRIPTION
|
||||||
|
Removes a Cmdlet from an REST-Api endpoints whitelist or blacklist.
|
||||||
|
.FUNCTIONALITY
|
||||||
|
Removes Cmdlets for REST-Api endpoints
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Remove-IcingaRESTApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker';
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Add-IcingaRESTApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker' -Blacklist;
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-restapi
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Remove-IcingaRESTApiCommand()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[string]$Command = '',
|
||||||
|
[string]$Endpoint = '',
|
||||||
|
[switch]$Blacklist = $FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($Command) -Or [string]::IsNullOrEmpty($Endpoint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Commands = $null;
|
||||||
|
$ConfigPath = ([string]::Format('RESTApi.Commands.{0}.Whitelist', $Endpoint));
|
||||||
|
[array]$Values = @();
|
||||||
|
|
||||||
|
if ($Blacklist) {
|
||||||
|
$ConfigPath = ([string]::Format('RESTApi.Commands.{0}.Blacklist', $Endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
$Commands = Get-IcingaPowerShellConfig -Path $ConfigPath;
|
||||||
|
|
||||||
|
if ($null -eq $Commands) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($element in $Commands) {
|
||||||
|
if ($element.ToLower() -ne $Command.ToLower()) {
|
||||||
|
$Values += $element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-IcingaPowerShellConfig -Path $ConfigPath -Value $Values;
|
||||||
|
}
|
||||||
44
lib/daemons/RestAPI/tools/Show-IcingaApiCommands.psm1
Normal file
44
lib/daemons/RestAPI/tools/Show-IcingaApiCommands.psm1
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tests if a Cmdlet for a given endpoint is configured as whitelist or blacklist.
|
||||||
|
This function will return True if the command is whitelisted and False if it is
|
||||||
|
blacklisted. If the Cmdlet is not added anywhere, the function will return False as well.
|
||||||
|
.DESCRIPTION
|
||||||
|
Tests if a Cmdlet for a given endpoint is configured as whitelist or blacklist.
|
||||||
|
This function will return True if the command is whitelisted and False if it is
|
||||||
|
blacklisted. If the Cmdlet is not added anywhere, the function will return False as well.
|
||||||
|
.FUNCTIONALITY
|
||||||
|
Tests if a Cmdlet is allowed to be executed over the REST-Api
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Test-IcingaRESTApiCommand -Command 'Invoke-IcingaCheckCPU' -Endpoint 'checker';
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-framework
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Show-IcingaRESTApiCommands()
|
||||||
|
{
|
||||||
|
$Commands = Get-IcingaPowerShellConfig -Path 'RESTApi.Commands';
|
||||||
|
|
||||||
|
foreach ($property in $Commands.PSObject.Properties) {
|
||||||
|
$Whitelisted = 'None';
|
||||||
|
$Blacklisted = 'None';
|
||||||
|
|
||||||
|
if (Test-IcingaPowerShellConfigItem -ConfigObject $property.Value -ConfigKey 'Whitelist') {
|
||||||
|
if ($property.Value.Whitelist.Count -ne 0) {
|
||||||
|
$Whitelisted = [string]::Join(', ', ($property.Value.Whitelist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Test-IcingaPowerShellConfigItem -ConfigObject $property.Value -ConfigKey 'Blacklist') {
|
||||||
|
if ($property.Value.Blacklist.Count -ne 0) {
|
||||||
|
$Blacklisted = [string]::Join(', ', ($property.Value.Blacklist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-IcingaConsolePlain -Message 'API Endpoint "{0}"' -Objects $property.Name;
|
||||||
|
Write-IcingaConsolePlain -Message '################';
|
||||||
|
Write-IcingaConsolePlain -Message '';
|
||||||
|
Write-IcingaConsolePlain -Message 'Whitelisted: {0}' -Objects $Whitelisted;
|
||||||
|
Write-IcingaConsolePlain -Message '';
|
||||||
|
Write-IcingaConsolePlain -Message 'Blacklisted: {0}' -Objects $Blacklisted;
|
||||||
|
Write-IcingaConsolePlain -Message '';
|
||||||
|
}
|
||||||
|
}
|
||||||
46
lib/daemons/RestAPI/tools/Test-IcingaRESTApiCommand.psm1
Normal file
46
lib/daemons/RestAPI/tools/Test-IcingaRESTApiCommand.psm1
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tests if a Cmdlet for a given endpoint is configured as whitelist or blacklist.
|
||||||
|
This function will return True if the command is whitelisted and False if it is
|
||||||
|
blacklisted. If the Cmdlet is not added anywhere, the function will return False as well.
|
||||||
|
.DESCRIPTION
|
||||||
|
Tests if a Cmdlet for a given endpoint is configured as whitelist or blacklist.
|
||||||
|
This function will return True if the command is whitelisted and False if it is
|
||||||
|
blacklisted. If the Cmdlet is not added anywhere, the function will return False as well.
|
||||||
|
.FUNCTIONALITY
|
||||||
|
Tests if a Cmdlet is allowed to be executed over the REST-Api
|
||||||
|
.EXAMPLE
|
||||||
|
PS>Test-IcingaRESTApiCommand -Command 'Invoke-IcingaCheckCPU' -Endpoint 'checker';
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-framework
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Test-IcingaRESTApiCommand()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[string]$Command = '',
|
||||||
|
[string]$Endpoint = ''
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($Command) -Or [string]::IsNullOrEmpty($Endpoint)) {
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$WhiteList = Get-IcingaPowerShellConfig -Path ([string]::Format('RESTApi.Commands.{0}.Whitelist', $Endpoint));
|
||||||
|
$Blacklist = Get-IcingaPowerShellConfig -Path ([string]::Format('RESTApi.Commands.{0}.Blacklist', $Endpoint));
|
||||||
|
|
||||||
|
foreach ($entry in $Blacklist) {
|
||||||
|
if ($Command.ToLower() -like $entry.ToLower()) {
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($entry in $WhiteList) {
|
||||||
|
if ($Command.ToLower() -like $entry.ToLower()) {
|
||||||
|
return $TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the command is not configured, always return false
|
||||||
|
return $FALSE;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
function Invoke-IcingaApiChecksRESTCall()
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[Hashtable]$Request = @{ },
|
||||||
|
[Hashtable]$Connection = @{ },
|
||||||
|
$IcingaGlobals,
|
||||||
|
[string]$ApiVersion = $null
|
||||||
|
);
|
||||||
|
|
||||||
|
$Global:IcingaDaemonData = $IcingaGlobals;
|
||||||
|
|
||||||
|
# Initialise some global variables we use to actually store check result data from
|
||||||
|
# plugins properly. This is doable from each thread instance as this part isn't
|
||||||
|
# shared between daemons
|
||||||
|
New-IcingaCheckSchedulerEnvironment;
|
||||||
|
|
||||||
|
[Hashtable]$ContentResponse = @{ };
|
||||||
|
|
||||||
|
# Short our call
|
||||||
|
$CheckerAliases = $IcingaGlobals.BackgroundDaemon.IcingaPowerShellRestApi.CommandAliases.checker;
|
||||||
|
$CheckConfig = $Request.Body;
|
||||||
|
[int]$ExitCode = 3; #Unknown
|
||||||
|
|
||||||
|
# Check if there are an inventory aliases configured
|
||||||
|
# This should be maintained by the developer and not occur
|
||||||
|
# anyway
|
||||||
|
if ($null -eq $CheckerAliases) {
|
||||||
|
$CheckerAliases = @{ };
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Get-IcingaRESTHeaderValue -Request $Request -Header 'Content-Type') -ne 'application/json' -And $Request.Method -eq 'POST') {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Bad Request') `
|
||||||
|
-ContentBody 'This API endpoint does only accept "application/json" as content type over POST.'
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Our namespace to include inventory packages is 'include' over the api
|
||||||
|
# Everything else will be dropped for the moment
|
||||||
|
if ($Request.RequestArguments.ContainsKey('list')) {
|
||||||
|
|
||||||
|
Add-IcingaHashtableItem `
|
||||||
|
-Hashtable $ContentResponse `
|
||||||
|
-Key 'Commands' `
|
||||||
|
-Value $CheckerAliases | Out-Null;
|
||||||
|
|
||||||
|
} elseif ($Request.RequestArguments.ContainsKey('command')) {
|
||||||
|
[string]$ExecuteCommand = $null;
|
||||||
|
|
||||||
|
foreach ($element in $CheckerAliases.Keys) {
|
||||||
|
if ($Request.RequestArguments.command -Contains $element) {
|
||||||
|
$ExecuteCommand = $CheckerAliases[$element];
|
||||||
|
# We only support to execute one check per call
|
||||||
|
# No need to loop through everything
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($ExecuteCommand)) {
|
||||||
|
[string]$ExecuteCommand = $Request.RequestArguments.command;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Test-IcingaRESTApiCommand -Command $ExecuteCommand -Endpoint 'apichecks') -eq $FALSE) {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Forbidden') `
|
||||||
|
-ContentBody ([string]::Format('The command "{0}" you are trying to execute over this REST-Api endpoint "apichecks" is not whitelisted for remote execution.', $ExecuteCommand))
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage -Message ('Executing API check for command: ' + $ExecuteCommand);
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($CheckConfig) -eq $FALSE -And $Request.Method -eq 'POST') {
|
||||||
|
# Convert our JSON config for checks to a PSCustomObject
|
||||||
|
$PSArguments = ConvertFrom-Json -InputObject $CheckConfig;
|
||||||
|
|
||||||
|
# For executing the checks, we will require the data as
|
||||||
|
# hashtable, so declare it here
|
||||||
|
[hashtable]$Arguments = @{ };
|
||||||
|
|
||||||
|
# Now convert our custom object by Key<->Value to
|
||||||
|
# a valid hashtable, allowing us to parse arguments
|
||||||
|
# to our check command
|
||||||
|
$PSArguments.PSObject.Properties | ForEach-Object {
|
||||||
|
Add-IcingaHashtableItem `
|
||||||
|
-Hashtable $Arguments `
|
||||||
|
-Key $_.Name `
|
||||||
|
-Value $_.Value | Out-Null;
|
||||||
|
};
|
||||||
|
|
||||||
|
[int]$ExitCode = & $ExecuteCommand @Arguments;
|
||||||
|
} elseif ($Request.Method -eq 'GET') {
|
||||||
|
[int]$ExitCode = & $ExecuteCommand;
|
||||||
|
} else {
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.Ok) `
|
||||||
|
-ContentBody @{ 'message' = 'This API endpoint does only accept GET and POST methods for requests.' }
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Once the check is executed, the plugin output and the performance data are stored
|
||||||
|
# within a special cache map we can use for accessing
|
||||||
|
$CheckResult = Get-IcingaCheckSchedulerPluginOutput;
|
||||||
|
[array]$PerfData = Get-IcingaCheckSchedulerPerfData;
|
||||||
|
|
||||||
|
# Ensure our PerfData variable is always an array
|
||||||
|
if ($null -eq $PerfData -Or $PerfData.Count -eq 0) {
|
||||||
|
[array]$PerfData = @();
|
||||||
|
}
|
||||||
|
|
||||||
|
# Free our memory again
|
||||||
|
Clear-IcingaCheckSchedulerEnvironment;
|
||||||
|
|
||||||
|
Write-IcingaDebugMessage -Message 'Check Executed. Result below' -Objects $ExecuteCommand, $CheckResult, $PerfData, $ExitCode;
|
||||||
|
|
||||||
|
Add-IcingaHashtableItem `
|
||||||
|
-Hashtable $ContentResponse `
|
||||||
|
-Key $ExecuteCommand `
|
||||||
|
-Value @{
|
||||||
|
'exitcode' = $ExitCode;
|
||||||
|
'checkresult' = $CheckResult;
|
||||||
|
'perfdata' = $PerfData;
|
||||||
|
} | Out-Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ContentResponse.Count -eq 0) {
|
||||||
|
$ContentResponse.Add(
|
||||||
|
'message',
|
||||||
|
'Welcome to the Icinga for Windows API checker. To execute checks, please use the command parameter. For providing arguments, you will have to submit a post with JSON encoded arguments. Example: /v1/checker?command=Invoke-IcingaCheckCPU'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send the response to the client
|
||||||
|
Send-IcingaTCPClientMessage -Message (
|
||||||
|
New-IcingaTCPClientRESTMessage `
|
||||||
|
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.Ok) `
|
||||||
|
-ContentBody $ContentResponse
|
||||||
|
) -Stream $Connection.Stream;
|
||||||
|
}
|
||||||
74
lib/daemons/modules/ApiChecks/apichecks.psm1
Normal file
74
lib/daemons/modules/ApiChecks/apichecks.psm1
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Registers an endpoint called 'checker' in our REST-Api
|
||||||
|
.DESCRIPTION
|
||||||
|
This module will provide an endpoint for the Icinga for Windows REST-Api
|
||||||
|
https://github.com/Icinga/icinga-powershell-restapi
|
||||||
|
|
||||||
|
The endpoint itself is called 'checker' and will map available Cmdlets for
|
||||||
|
check plugins
|
||||||
|
.FUNCTIONALITY
|
||||||
|
This module will depend on the Icinga Plugins as well as the Framework and
|
||||||
|
the REST-Api. It will provide a wrapper to execute Icinga Plugins over the
|
||||||
|
REST-Api and fetch the plugin output
|
||||||
|
.OUTPUTS
|
||||||
|
$NULL
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-apichecks
|
||||||
|
.NOTES
|
||||||
|
#>
|
||||||
|
function Register-IcingaRESTAPIEndpointAPIChecks()
|
||||||
|
{
|
||||||
|
return @{
|
||||||
|
'Alias' = 'checker';
|
||||||
|
'Command' = 'Invoke-IcingaApiChecksRESTCall';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Registers our command aliases for mapping the 'command' argument
|
||||||
|
from our REST-Call securely to our PowerShell Cmdlets
|
||||||
|
.DESCRIPTION
|
||||||
|
This module will provide an endpoint for the Icinga for Windows REST-Api
|
||||||
|
https://github.com/Icinga/icinga-powershell-restapi
|
||||||
|
By using the references, we can register endpoints and aliases to fetch
|
||||||
|
informations
|
||||||
|
.EXAMPLE
|
||||||
|
https://example.com/v1/checker?command=cpu
|
||||||
|
.FUNCTIONALITY
|
||||||
|
This module will depend on the Icinga Plugins as well as the Framework and
|
||||||
|
the REST-Api. It will provide a wrapper to execute Icinga Plugins over the
|
||||||
|
REST-Api and fetch the plugin output
|
||||||
|
.OUTPUTS
|
||||||
|
$NULL
|
||||||
|
.LINK
|
||||||
|
https://github.com/Icinga/icinga-powershell-apichecks
|
||||||
|
.NOTES
|
||||||
|
#>
|
||||||
|
function Register-IcingaRESTApiCommandAliasesApiChecks()
|
||||||
|
{
|
||||||
|
[array]$CmdList = Get-Command -Name 'Invoke-IcingaCheck*';
|
||||||
|
[hashtable]$CmdAlias = @{
|
||||||
|
'checker' = @{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
$FrameworkRoot = Get-IcingaForWindowsRootPath;
|
||||||
|
|
||||||
|
foreach ($entry in $CmdList) {
|
||||||
|
# Only include checks being installed on the same location as our Framework
|
||||||
|
if ($entry.Module.Path.Contains($FrameworkRoot) -eq $FALSE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$CmdAliasName = $entry.Name.Replace('Invoke-IcingaCheck', '');
|
||||||
|
|
||||||
|
if ($CmdAlias.checker.ContainsKey($CmdAliasName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$CmdAlias.checker.Add($CmdAliasName, $Entry.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CmdAlias;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue