Skip to content

Commit 65f9f94

Browse files
authored
Merge pull request #383 from Icinga:feature/move_restapi_apichecks_into_framework
Feature: Move REST-Api and Aoi-Checks into Framework As the long-term goal will be to move away from check execution with local PowerShell instances, we move the REST-Api and Api-Checks feature directly into the Framework. By adding everything directly into the Framework, we ensure that no side-loading of modules is required and we can use the API and checker features to increase performance by a lot and provide direct setup and installation procedures inside the IMC.
2 parents fedc34b + 253cb7f commit 65f9f94

19 files changed

+1032
-0
lines changed

Diff for: doc/100-General/01-Upgrading.md

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ Upgrading Icinga PowerShell Framework is usually quite straightforward.
44

55
Specific version upgrades are described below. Please note that version updates are incremental.
66

7+
## Upgrading to v1.7.0 (2021-11-09)
8+
9+
### REST-Api and Api-Checks
10+
11+
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.
12+
13+
**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.
14+
15+
```powershell
16+
Uninstall-IcingaComponent -Name 'restapi';
17+
Uninstall-IcingaComponent -Name 'apichecks';
18+
```
19+
720
## Upgrading to v1.5.0 (2021-06-02)
821

922
### `SecureString` and Icinga Director Baskets

Diff for: doc/100-General/10-Changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
1717
* [#376](https://github.com/Icinga/icinga-powershell-framework/pull/376) Fixes IMC error handling on invalid JSON for installation command/file
1818
* [#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
1919

20+
### Enhancements
21+
22+
* [#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
23+
2024
## 1.6.1 (2021-09-15)
2125

2226
[Issue and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/21?closed=1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function Add-IcingaRESTClientBlacklistCount()
2+
{
3+
param (
4+
[System.Net.Sockets.TcpClient]$Client = $null,
5+
$ClientList = $null
6+
);
7+
8+
if ($null -eq $Client) {
9+
return;
10+
}
11+
12+
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
13+
[string]$IpAddress = $Endpoint.Split(':')[0];
14+
[int]$Value = Get-IcingaHashtableItem `
15+
-Hashtable $ClientList `
16+
-Key $IpAddress `
17+
-NullValue 0;
18+
19+
Add-IcingaHashtableItem `
20+
-Hashtable $ClientList `
21+
-Key $IpAddress `
22+
-Value ($Value + 1) `
23+
-Override | Out-Null;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function Invoke-IcingaRESTAPIv1Calls()
2+
{
3+
param (
4+
[Hashtable]$Request = @{},
5+
[Hashtable]$Connection = @{}
6+
);
7+
8+
[string]$ModuleToLoad = Get-IcingaRESTPathElement -Request $Request -Index 1;
9+
# Map our Icinga globals to a shorter variable
10+
$RestDaemon = $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi;
11+
12+
if ([string]::IsNullOrEmpty($ModuleToLoad)) {
13+
Send-IcingaTCPClientMessage -Message (
14+
New-IcingaTCPClientRESTMessage `
15+
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.Ok) `
16+
-ContentBody @{
17+
'Endpoints' = @(
18+
$RestDaemon.RegisteredEndpoints.Keys
19+
)
20+
}
21+
) -Stream $Connection.Stream;
22+
return;
23+
}
24+
25+
if ($RestDaemon.RegisteredEndpoints.ContainsKey($ModuleToLoad) -eq $FALSE) {
26+
Send-IcingaTCPClientMessage -Message (
27+
New-IcingaTCPClientRESTMessage `
28+
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Not Found') `
29+
-ContentBody 'There was no module found which is registered for this endpoint name.'
30+
) -Stream $Connection.Stream;
31+
return;
32+
}
33+
34+
[string]$Command = $RestDaemon.RegisteredEndpoints[$ModuleToLoad];
35+
36+
Write-IcingaDebugMessage -Message 'Executing REST-Module' -Objects $Command;
37+
38+
if ($null -eq (Get-Command $Command -ErrorAction SilentlyContinue)) {
39+
Send-IcingaTCPClientMessage -Message (
40+
New-IcingaTCPClientRESTMessage `
41+
-HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Internal Server Error') `
42+
-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.'
43+
) -Stream $Connection.Stream;
44+
return;
45+
}
46+
47+
[hashtable]$CommandArguments = @{
48+
'-Request' = $Request;
49+
'-Connection' = $Connection;
50+
'-IcingaGlobals' = $IcingaDaemonData;
51+
'-ApiVersion' = 'v1';
52+
};
53+
54+
& $Command @CommandArguments | Out-Null;
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function Remove-IcingaRESTClientBlacklist()
2+
{
3+
param (
4+
[System.Net.Sockets.TcpClient]$Client = $null,
5+
$ClientList = $null
6+
);
7+
8+
if ($null -eq $Client) {
9+
return;
10+
}
11+
12+
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
13+
[string]$IpAddress = $Endpoint.Split(':')[0];
14+
15+
Remove-IcingaHashtableItem `
16+
-Hashtable $ClientList `
17+
-Key $IpAddress;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function Test-IcingaRESTClientBlacklisted()
2+
{
3+
param (
4+
[System.Net.Sockets.TcpClient]$Client = $null,
5+
$ClientList = $null
6+
);
7+
8+
if ($null -eq $Client) {
9+
return $FALSE;
10+
}
11+
12+
[string]$Endpoint = Get-IcingaTCPClientRemoteEndpoint -Client $Client;
13+
[string]$IpAddress = $Endpoint.Split(':')[0];
14+
[int]$Value = Get-IcingaHashtableItem `
15+
-Hashtable $ClientList `
16+
-Key $IpAddress `
17+
-NullValue 0;
18+
19+
# After 9 invalid attempts we will blacklist the client
20+
if ($Value -gt 9) {
21+
Write-IcingaDebugMessage -Message 'Client is blacklisted' -Objects $IpAddress, $Value, $Client.Client;
22+
return $TRUE;
23+
}
24+
25+
return $FALSE;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function Test-IcingaRESTClientConnection()
2+
{
3+
param(
4+
[Hashtable]$Connection = @{}
5+
);
6+
7+
# If we couldn't establish a proper SSL stream, close the connection
8+
# immediately without opening a client connection thread
9+
if ($null -eq $Connection.Stream) {
10+
Add-IcingaRESTClientBlacklistCount `
11+
-Client $Connection.Client `
12+
-ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
13+
Write-IcingaEventMessage -EventId 1501 -Namespace 'Framework' -Objects $Connection.Client.Client;
14+
Close-IcingaTCPConnection -Client $Connection.Client;
15+
$Connection = $null;
16+
return $FALSE;
17+
}
18+
19+
Write-IcingaDebugMessage 'Client connection has passed Test';
20+
21+
return $TRUE;
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
function New-IcingaForWindowsRESTApi()
2+
{
3+
# Allow us to parse the framework global data to this thread
4+
param (
5+
$IcingaDaemonData,
6+
$Port,
7+
$RootFolder,
8+
$CertFile,
9+
$CertThumbprint,
10+
$RequireAuth
11+
);
12+
13+
# Import the framework library components and initialise it
14+
# as daemon
15+
Use-Icinga -LibOnly -Daemon;
16+
17+
$Global:IcingaDaemonData = $IcingaDaemonData;
18+
19+
# Add a synchronized hashtable to the global data background
20+
# daemon hashtable to write data to. In addition it will
21+
# allow to share data collected from this daemon with others
22+
$IcingaDaemonData.BackgroundDaemon.Add(
23+
'IcingaPowerShellRestApi',
24+
[hashtable]::Synchronized(@{})
25+
);
26+
27+
# Map our Icinga globals to a shorter variable
28+
$RestDaemon = $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi;
29+
30+
# This will add another hashtable to our previous
31+
# IcingaPowerShellRestApi hashtable to store actual
32+
# endpoint configurations for the API
33+
$RestDaemon.Add(
34+
'RegisteredEndpoints',
35+
[hashtable]::Synchronized(@{})
36+
);
37+
38+
# This will add another hashtable to our previous
39+
# IcingaPowerShellRestApi hashtable to store actual
40+
# command aliases for execution for the API
41+
$RestDaemon.Add(
42+
'CommandAliases',
43+
[hashtable]::Synchronized(@{})
44+
);
45+
46+
# This will add another hashtable to our previous
47+
# IcingaPowerShellRestApi hashtable to store actual
48+
# command aliases for execution for the API
49+
$RestDaemon.Add(
50+
'ClientBlacklist',
51+
[hashtable]::Synchronized(@{})
52+
);
53+
54+
# Make the root folder of our rest daemon module available
55+
# for every possible thread we require
56+
$RestDaemon.Add(
57+
'RootFolder', $RootFolder
58+
);
59+
60+
$RESTEndpoints = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTAPIEndpoint*';
61+
Write-IcingaDebugMessage -Message (
62+
[string]::Format(
63+
'Loading configuration for REST-Endpoints{0}{1}',
64+
(New-IcingaNewLine),
65+
($RESTEndpoints | Out-String)
66+
)
67+
);
68+
69+
Write-IcingaDebugMessage -Message ($RestDaemon | Out-String);
70+
71+
foreach ($entry in $RESTEndpoints.Values) {
72+
[bool]$Success = Add-IcingaHashtableItem -Hashtable $RestDaemon.RegisteredEndpoints `
73+
-Key $entry.Alias `
74+
-Value $entry.Command;
75+
76+
if ($Success -eq $FALSE) {
77+
Write-IcingaEventMessage `
78+
-EventId 2100 `
79+
-Namespace 'RESTApi' `
80+
-Objects ([string]::Format('Adding duplicated REST endpoint "{0}" with command "{1}', $entry.Alias, $entry.Command)), $RESTEndpoints;
81+
}
82+
}
83+
84+
$CommandAliases = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaRESTApiCommandAliases*';
85+
86+
foreach ($entry in $CommandAliases.Values) {
87+
foreach ($component in $entry.Keys) {
88+
[bool]$Success = Add-IcingaHashtableItem -Hashtable $RestDaemon.CommandAliases `
89+
-Key $component `
90+
-Value $entry[$component];
91+
92+
if ($Success -eq $FALSE) {
93+
Write-IcingaEventMessage `
94+
-EventId 2101 `
95+
-Namespace 'RESTApi' `
96+
-Objects ([string]::Format('Adding duplicated REST command aliases "{0}" for namespace "{1}', $entry[$component], $component)), $CommandAliases;
97+
}
98+
}
99+
}
100+
101+
Write-IcingaDebugMessage -Message ($RestDaemon.RegisteredEndpoints | Out-String);
102+
103+
if ($Global:IcingaDaemonData.JEAContext) {
104+
if ($global:IcingaDaemonData.ContainsKey('SSLCertificate') -eq $FALSE -Or $null -eq $global:IcingaDaemonData.SSLCertificate) {
105+
Write-IcingaEventMessage -EventId 2001 -Namespace 'RESTApi';
106+
return;
107+
}
108+
109+
$Certificate = $global:IcingaDaemonData.SSLCertificate;
110+
} else {
111+
$Certificate = Get-IcingaSSLCertForSocket -CertFile $CertFile -CertThumbprint $CertThumbprint;
112+
}
113+
114+
if ($null -eq $Certificate) {
115+
Write-IcingaEventMessage -EventId 2000 -Namespace 'RESTApi';
116+
return;
117+
}
118+
119+
$Socket = New-IcingaTCPSocket -Address $Address -Port $Port -Start;
120+
121+
# Keep our code executed as long as the PowerShell service is
122+
# being executed. This is required to ensure we will execute
123+
# the code frequently instead of only once
124+
while ($TRUE) {
125+
$Connection = Open-IcingaTCPClientConnection `
126+
-Client (New-IcingaTCPClient -Socket $Socket) `
127+
-Certificate $Certificate;
128+
129+
if (Test-IcingaRESTClientBlacklisted -Client $Connection.Client -ClientList $RestDaemon.ClientBlacklist) {
130+
Write-IcingaDebugMessage -Message 'A remote client which is trying to connect was blacklisted' -Objects $Connection.Client.Client;
131+
Close-IcingaTCPConnection -Client $Connection.Client;
132+
continue;
133+
}
134+
135+
if ((Test-IcingaRESTClientConnection -Connection $Connection) -eq $FALSE) {
136+
continue;
137+
}
138+
139+
# API not yet ready
140+
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.Count -eq 0) {
141+
Close-IcingaTCPConnection -Client $Connection.Client;
142+
continue;
143+
}
144+
145+
try {
146+
$NextRESTApiThreadId = (Get-IcingaNextRESTApiThreadId);
147+
148+
if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.ContainsKey($NextRESTApiThreadId) -eq $FALSE) {
149+
Close-IcingaTCPConnection -Client $Connection.Client;
150+
continue;
151+
}
152+
153+
$IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$NextRESTApiThreadId.Enqueue($Connection);
154+
} catch {
155+
$ExMsg = $_.Exception.Message;
156+
Write-IcingaEventMessage -Namespace 'RESTApi' -EvenId 2050 -Objects $ExMsg;
157+
}
158+
159+
# Cleanup the error stack and remove not required data
160+
$Error.Clear();
161+
# Force PowerShell to call the garbage collector to free memory
162+
[System.GC]::Collect();
163+
}
164+
}

0 commit comments

Comments
 (0)