From 5770956533d070bb3aecc69a34a7c05f4ca98067 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 11:13:01 +0100 Subject: [PATCH 01/46] Add support to create X509 certificates based on .crt/.cert Thanks to @crited --- lib/web/ConvertTo-IcingaX509Certificate.psm1 | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lib/web/ConvertTo-IcingaX509Certificate.psm1 diff --git a/lib/web/ConvertTo-IcingaX509Certificate.psm1 b/lib/web/ConvertTo-IcingaX509Certificate.psm1 new file mode 100644 index 0000000..2bba530 --- /dev/null +++ b/lib/web/ConvertTo-IcingaX509Certificate.psm1 @@ -0,0 +1,48 @@ +function ConvertTo-IcingaX509Certificate() +{ + param( + [string]$CertFile = $null, + [string]$OutFile = $null, + [switch]$Force = $FALSE + ); + + # Use an empty password for converted certificates + $Password = $null; + # Use a target file to specify if we use temp files or not + $TargetFile = $null; + # Temp Cert + [bool]$TempFile = $FALSE; + + # Create a temp file to store the certificate in + if ([string]::IsNullOrEmpty($OutFile)) { + # Create a temporary file for full path and name + $TargetFile = New-IcingaTemporaryFile; + # Get the actual path to work with + $TargetFile = $TargetFile.FullName; + # Set internally that we are using a temp file + $TempFile = $TRUE; + # Delete the file again + Remove-Item $TargetFile -Force -ErrorAction SilentlyContinue; + } + + # Convert our certificate if our target file does not exist + # it is a temp file or we force its creation + if (-Not (Test-Path $TargetFile) -Or $TempFile -Or $Force) { + Write-Output "$Password + $Password" | certutil -mergepfx "$CertFile" "$TargetFile" | Out-Null; + } + + # If no target file exists afterwards (a valid PFX certificate) + # then throw an exception + if (-Not (Test-Path $TargetFile)) { + throw 'The specified/created certificate file could not be found.'; + } + + # Now load the actual certificate from the path + $Certificate = New-Object Security.Cryptography.X509Certificates.X509Certificate2 $TargetFile; + # Delete the PFX-Certificate which will be present after certutil merge + Remove-Item $TargetFile -Force -ErrorAction SilentlyContinue; + + # Return the certificate + return $Certificate +} From edd8b4b6eda21ff086818be3f70b24588d37a4e3 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 12:42:14 +0100 Subject: [PATCH 02/46] Added basiic TCP Cmdlets and handling --- lib/core/tools/Close-IcingaTcpConnection.psm1 | 14 --------- lib/web/Close-IcingaTCPConnection.psm1 | 14 +++++++++ lib/web/Close-IcingaTCPSocket.psm1 | 12 ++++++++ lib/web/New-IcingaSSLStream.psm1 | 12 ++++++++ lib/web/New-IcingaTCPClient.psm1 | 14 +++++++++ lib/web/New-IcingaTCPSocket.psm1 | 19 ++++++++++++ lib/web/Read-IcingaTCPStream.psm1 | 29 +++++++++++++++++++ 7 files changed, 100 insertions(+), 14 deletions(-) delete mode 100644 lib/core/tools/Close-IcingaTcpConnection.psm1 create mode 100644 lib/web/Close-IcingaTCPConnection.psm1 create mode 100644 lib/web/Close-IcingaTCPSocket.psm1 create mode 100644 lib/web/New-IcingaSSLStream.psm1 create mode 100644 lib/web/New-IcingaTCPClient.psm1 create mode 100644 lib/web/New-IcingaTCPSocket.psm1 create mode 100644 lib/web/Read-IcingaTCPStream.psm1 diff --git a/lib/core/tools/Close-IcingaTcpConnection.psm1 b/lib/core/tools/Close-IcingaTcpConnection.psm1 deleted file mode 100644 index 65b532a..0000000 --- a/lib/core/tools/Close-IcingaTcpConnection.psm1 +++ /dev/null @@ -1,14 +0,0 @@ -function Close-IcingaTcpConnection() -{ - param( - $TcpClient - ); - - if ($null -eq $TcpClient) { - return; - } - - $TcpClient.Close(); - $TcpClient.Dispose(); - $TcpClient = $null; -} diff --git a/lib/web/Close-IcingaTCPConnection.psm1 b/lib/web/Close-IcingaTCPConnection.psm1 new file mode 100644 index 0000000..03103b1 --- /dev/null +++ b/lib/web/Close-IcingaTCPConnection.psm1 @@ -0,0 +1,14 @@ +function Close-IcingaTCPConnection() +{ + param( + [System.Net.Sockets.TcpClient]$Client = $null + ); + + if ($null -eq $Client) { + return; + } + + $Client.Close(); + $Client.Dispose(); + $Client = $null; +} diff --git a/lib/web/Close-IcingaTCPSocket.psm1 b/lib/web/Close-IcingaTCPSocket.psm1 new file mode 100644 index 0000000..05ef283 --- /dev/null +++ b/lib/web/Close-IcingaTCPSocket.psm1 @@ -0,0 +1,12 @@ +function Close-IcingaTCPSocket() +{ + param( + [System.Net.Sockets.TcpListener]$Socket = $null + ); + + if ($null -eq $Socket) { + return; + } + + $Socket.Stop(); +} diff --git a/lib/web/New-IcingaSSLStream.psm1 b/lib/web/New-IcingaSSLStream.psm1 new file mode 100644 index 0000000..6ffc693 --- /dev/null +++ b/lib/web/New-IcingaSSLStream.psm1 @@ -0,0 +1,12 @@ +function New-IcingaSSLStream() +{ + param( + [System.Net.Sockets.TcpClient]$Client = @{}, + [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate = $null + ); + + $SSLStream = New-Object System.Net.Security.SslStream($Client.GetStream(), $false) + $SSLStream.AuthenticateAsServer($Certificate, $false, [System.Security.Authentication.SslProtocols]::Tls12, $true) | Out-Null; + + return $SSLStream; +} diff --git a/lib/web/New-IcingaTCPClient.psm1 b/lib/web/New-IcingaTCPClient.psm1 new file mode 100644 index 0000000..a110aa7 --- /dev/null +++ b/lib/web/New-IcingaTCPClient.psm1 @@ -0,0 +1,14 @@ +function New-IcingaTCPClient() +{ + param( + [System.Net.Sockets.TcpListener]$Socket = $null + ); + + if ($null -eq $Socket) { + return $null; + } + + [System.Net.Sockets.TcpClient]$Client = $Socket.AcceptTcpClient(); + + return $Client; +} diff --git a/lib/web/New-IcingaTCPSocket.psm1 b/lib/web/New-IcingaTCPSocket.psm1 new file mode 100644 index 0000000..ea8596c --- /dev/null +++ b/lib/web/New-IcingaTCPSocket.psm1 @@ -0,0 +1,19 @@ +function New-IcingaTCPSocket() +{ + param( + [int]$Port = 0, + [switch]$Start = $FALSE + ); + + if ($Port -eq 0) { + throw 'Please specify a valid port to open a TCP socket for'; + } + + $TCPSocket = [System.Net.Sockets.TcpListener]$Port; + + if ($Start) { + $TCPSocket.Start(); + } + + return $TCPSocket; +} diff --git a/lib/web/Read-IcingaTCPStream.psm1 b/lib/web/Read-IcingaTCPStream.psm1 new file mode 100644 index 0000000..3ff3d6e --- /dev/null +++ b/lib/web/Read-IcingaTCPStream.psm1 @@ -0,0 +1,29 @@ +function Read-IcingaTCPStream() +{ + param( + [System.Net.Sockets.TcpClient]$Client = @{}, + [System.Net.Security.SslStream]$Stream = $null + ); + + # Get the maxium size of our buffer + [byte[]]$bytes = New-Object byte[] $Client.ReceiveBufferSize; + # Read the content of our SSL stream + $Stream.Read($bytes, 0, $Client.ReceiveBufferSize); + + # Now ready the actual content size of the received message + [int]$count = 0; + for ($i=0; $i -le $Client.ReceiveBufferSize; $i++) { + if ($bytes[$i] -ne 0) { + $count = $count + 1; + } else { + break; + } + } + + # Resize our array to the correct size + [byte[]]$resized = New-Object byte[] $count; + [array]::Copy($bytes, 0, $resized, 0, $count); + + # Return our message content + return [System.Text.Encoding]::UTF8.GetString($resized); +} From b947f71f3ab709cb12332640c4aa328811ea1328 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 14:04:41 +0100 Subject: [PATCH 03/46] Add support to write into Windows Event Log (debug only for now) --- icinga-powershell-framework.psm1 | 8 ++++++-- .../framework/Disable-IcingaFrameworkDebugMode.psm1 | 4 ++++ .../framework/Enable-IcingaFrameworkDebugMode.psm1 | 4 ++++ lib/core/logging/Register-IcingaEventLog.psm1 | 12 ++++++++++++ lib/core/logging/Write-IcingaDebugMessage.psm1 | 12 ++++++++++++ 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 create mode 100644 lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 create mode 100644 lib/core/logging/Register-IcingaEventLog.psm1 create mode 100644 lib/core/logging/Write-IcingaDebugMessage.psm1 diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index 2818aa0..640926c 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -12,8 +12,9 @@ function Use-Icinga() { param( - [switch]$LibOnly = $FALSE, - [switch]$Daemon = $FALSE + [switch]$LibOnly = $FALSE, + [switch]$Daemon = $FALSE, + [switch]$DebugMode = $FALSE ); # Ensure we autoload the Icinga Plugin collection, provided by the external @@ -29,6 +30,8 @@ function Use-Icinga() Import-IcingaLib '\' -Init; if ($LibOnly -eq $FALSE) { + Register-IcingaEventLog; + $global:IcingaThreads = [hashtable]::Synchronized(@{}); $global:IcingaThreadContent = [hashtable]::Synchronized(@{}); $global:IcingaThreadPool = [hashtable]::Synchronized(@{}); @@ -38,6 +41,7 @@ function Use-Icinga() 'IcingaThreadContent' = $global:IcingaThreadContent; 'IcingaThreadPool' = $global:IcingaThreadPool; 'FrameworkRunningAsDaemon' = $Daemon; + 'DebugMode' = $DebugMode; } ); } diff --git a/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 new file mode 100644 index 0000000..ebd30f5 --- /dev/null +++ b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 @@ -0,0 +1,4 @@ +function Disable-IcingaFrameworkDebugMode() +{ + $global:IcingaDaemonData.DebugMode = $FALSE; +} diff --git a/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 new file mode 100644 index 0000000..9ee7d15 --- /dev/null +++ b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 @@ -0,0 +1,4 @@ +function Enable-IcingaFrameworkDebugMode() +{ + $global:IcingaDaemonData.DebugMode = $TRUE; +} diff --git a/lib/core/logging/Register-IcingaEventLog.psm1 b/lib/core/logging/Register-IcingaEventLog.psm1 new file mode 100644 index 0000000..0ed2167 --- /dev/null +++ b/lib/core/logging/Register-IcingaEventLog.psm1 @@ -0,0 +1,12 @@ +function Register-IcingaEventLog() +{ + $Registered = [System.Diagnostics.EventLog]::SourceExists( + 'Icinga for Windows' + ); + + if ($Registered) { + return; + } + + New-EventLog -LogName Application -Source 'Icinga for Windows'; +} diff --git a/lib/core/logging/Write-IcingaDebugMessage.psm1 b/lib/core/logging/Write-IcingaDebugMessage.psm1 new file mode 100644 index 0000000..473d129 --- /dev/null +++ b/lib/core/logging/Write-IcingaDebugMessage.psm1 @@ -0,0 +1,12 @@ +function Write-IcingaDebugMessage() +{ + param( + [string]$Message + ); + + if ($global:IcingaDaemonData.DebugMode -eq $FALSE) { + return; + } + + Write-EventLog -LogName Application -Source 'Icinga for Windows' -EntryType Information -EventId 1000 -Message $Message; +} From 08e35940084d5923f9a6967029670a7ec060dcda Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 15:32:14 +0100 Subject: [PATCH 04/46] Add debug output messages for TCP handling --- lib/web/Close-IcingaTCPConnection.psm1 | 7 +++++++ lib/web/Close-IcingaTCPSocket.psm1 | 7 +++++++ lib/web/New-IcingaTCPClient.psm1 | 7 +++++++ lib/web/New-IcingaTCPSocket.psm1 | 14 ++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/lib/web/Close-IcingaTCPConnection.psm1 b/lib/web/Close-IcingaTCPConnection.psm1 index 03103b1..81388ae 100644 --- a/lib/web/Close-IcingaTCPConnection.psm1 +++ b/lib/web/Close-IcingaTCPConnection.psm1 @@ -8,6 +8,13 @@ function Close-IcingaTCPConnection() return; } + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Closing client connection for endpoint {0}', + $Client.Client.RemoteEndPoint + ) + ); + $Client.Close(); $Client.Dispose(); $Client = $null; diff --git a/lib/web/Close-IcingaTCPSocket.psm1 b/lib/web/Close-IcingaTCPSocket.psm1 index 05ef283..c11417d 100644 --- a/lib/web/Close-IcingaTCPSocket.psm1 +++ b/lib/web/Close-IcingaTCPSocket.psm1 @@ -8,5 +8,12 @@ function Close-IcingaTCPSocket() return; } + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Closing TCP socket {0}', + $Socket.LocalEndpoint + ) + ); + $Socket.Stop(); } diff --git a/lib/web/New-IcingaTCPClient.psm1 b/lib/web/New-IcingaTCPClient.psm1 index a110aa7..e3256ec 100644 --- a/lib/web/New-IcingaTCPClient.psm1 +++ b/lib/web/New-IcingaTCPClient.psm1 @@ -10,5 +10,12 @@ function New-IcingaTCPClient() [System.Net.Sockets.TcpClient]$Client = $Socket.AcceptTcpClient(); + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'New incoming client connection for endpoint {0}', + $Client.Client.RemoteEndPoint + ) + ); + return $Client; } diff --git a/lib/web/New-IcingaTCPSocket.psm1 b/lib/web/New-IcingaTCPSocket.psm1 index ea8596c..916dfcb 100644 --- a/lib/web/New-IcingaTCPSocket.psm1 +++ b/lib/web/New-IcingaTCPSocket.psm1 @@ -11,7 +11,21 @@ function New-IcingaTCPSocket() $TCPSocket = [System.Net.Sockets.TcpListener]$Port; + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Creating new TCP socket on Port {0}. Endpoint configuration {1}', + $Port, + $TCPSocket.LocalEndpoint + ) + ); + if ($Start) { + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Starting TCP socket for endpoint {0}', + $TCPSocket.LocalEndpoint + ) + ); $TCPSocket.Start(); } From 50133d22f21726892ceb51b3af62945a197e3b38 Mon Sep 17 00:00:00 2001 From: Alexander Stoll <45196568+Crited@users.noreply.github.com> Date: Tue, 24 Mar 2020 18:40:33 +0100 Subject: [PATCH 05/46] Create Read-IcingaRESTMessage.psm1 First draft of Read-IcingaRESTMessage.psm1 #57 Regarding parsing --- lib/core/tools/Read-IcingaRESTMessage.psm1 | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/core/tools/Read-IcingaRESTMessage.psm1 diff --git a/lib/core/tools/Read-IcingaRESTMessage.psm1 b/lib/core/tools/Read-IcingaRESTMessage.psm1 new file mode 100644 index 0000000..f2acd03 --- /dev/null +++ b/lib/core/tools/Read-IcingaRESTMessage.psm1 @@ -0,0 +1,58 @@ +function Read-IcingaRESTMessage() +{ + param( + [string]$RestMessage = $null + ); + + [hashtable]$Request = @{} + $RestMessage -match '(\d+) (.+) (.+) (.+)' | Out-Null + + $Request.Add('MessageLength', $Matches[1]); + $Request.Add('Method', $Matches[2]); + $Request.Add('FullRequest', $Matches[3]); + $Request.Add('RequestPath', @{}); + $Request.Add('RequestArguments', @{}); + + #Path + $PathMatch = $Matches[3] + $PathMatch -match '(.+)\?(.*)' | Out-Null + $Path = $Matches[1] + $Arguments = $Matches[2] + $Request.RequestPath.Add('FullPath', $Matches[1]); + $Request.RequestPath.Add('PathArray', $Matches[1].TrimStart('/').Split('/')); + + $Matches = $null + + # Arguments + $ArgumentsSplit = $Arguments.Split('&') + $ArgumentsSplit+='\\\\\\\\\\\\=FIN' + foreach ( $Argument in $ArgumentsSplit | sort -descending) { + $Argument -match '(.+)=(.+)' | Out-Null + If (($Matches[1] -ne $Current) -And ($NULL -ne $Current)) { + $Request.RequestArguments.Add( $Current, $ArgumentContent ); + [array]$ArgumentContent = $null + } + $Current = $Matches[1] + [array]$ArgumentContent+=($Matches[2]); + } + + # Header + $Request.Add( 'Header', @{} ); + $SplitString = $RestMessage.split("`r`n") + foreach ( $SingleString in $SplitString ) { + if ( ([string]::IsNullOrEmpty($SingleString) -eq $FALSE) -And ($SingleString -match '^{.+' -eq $FALSE) ) { + $SingleSplitString = $SingleString.Split(':',2); + $Request.Header.Add( $SingleSplitString[0], $SingleSplitString[1].Trim()); + } + } + + $Request.Add('ContentLength', [int](Get-IcingaRESTHeaderValue -Header 'Content-Length' -Request $Request)); + + $Matches = $null + + # Body + $RestMessage -match '(\{(.*\n)*}|\{.*\})' | Out-Null + $Request.Add('Body', $Matches[1]); + + return $Request; +} From e6b46b84f7dc6f82cb2d0ea17ffbda2e0d04bf48 Mon Sep 17 00:00:00 2001 From: Alexander Stoll <45196568+Crited@users.noreply.github.com> Date: Tue, 24 Mar 2020 18:43:27 +0100 Subject: [PATCH 06/46] Fix codestyling --- lib/core/tools/Read-IcingaRESTMessage.psm1 | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/core/tools/Read-IcingaRESTMessage.psm1 b/lib/core/tools/Read-IcingaRESTMessage.psm1 index f2acd03..ffed571 100644 --- a/lib/core/tools/Read-IcingaRESTMessage.psm1 +++ b/lib/core/tools/Read-IcingaRESTMessage.psm1 @@ -4,8 +4,8 @@ function Read-IcingaRESTMessage() [string]$RestMessage = $null ); - [hashtable]$Request = @{} - $RestMessage -match '(\d+) (.+) (.+) (.+)' | Out-Null + [hashtable]$Request = @{}; + $RestMessage -match '(\d+) (.+) (.+) (.+)' | Out-Null; $Request.Add('MessageLength', $Matches[1]); $Request.Add('Method', $Matches[2]); @@ -14,31 +14,31 @@ function Read-IcingaRESTMessage() $Request.Add('RequestArguments', @{}); #Path - $PathMatch = $Matches[3] + $PathMatch = $Matches[3]; $PathMatch -match '(.+)\?(.*)' | Out-Null - $Path = $Matches[1] - $Arguments = $Matches[2] + $Path = $Matches[1]; + $Arguments = $Matches[2]; $Request.RequestPath.Add('FullPath', $Matches[1]); $Request.RequestPath.Add('PathArray', $Matches[1].TrimStart('/').Split('/')); - $Matches = $null + $Matches = $null; # Arguments - $ArgumentsSplit = $Arguments.Split('&') - $ArgumentsSplit+='\\\\\\\\\\\\=FIN' + $ArgumentsSplit = $Arguments.Split('&'); + $ArgumentsSplit+='\\\\\\\\\\\\=FIN'; foreach ( $Argument in $ArgumentsSplit | sort -descending) { - $Argument -match '(.+)=(.+)' | Out-Null + $Argument -match '(.+)=(.+)' | Out-Null; If (($Matches[1] -ne $Current) -And ($NULL -ne $Current)) { $Request.RequestArguments.Add( $Current, $ArgumentContent ); - [array]$ArgumentContent = $null + [array]$ArgumentContent = $null; } - $Current = $Matches[1] + $Current = $Matches[1]; [array]$ArgumentContent+=($Matches[2]); } # Header $Request.Add( 'Header', @{} ); - $SplitString = $RestMessage.split("`r`n") + $SplitString = $RestMessage.split("`r`n"); foreach ( $SingleString in $SplitString ) { if ( ([string]::IsNullOrEmpty($SingleString) -eq $FALSE) -And ($SingleString -match '^{.+' -eq $FALSE) ) { $SingleSplitString = $SingleString.Split(':',2); @@ -48,10 +48,10 @@ function Read-IcingaRESTMessage() $Request.Add('ContentLength', [int](Get-IcingaRESTHeaderValue -Header 'Content-Length' -Request $Request)); - $Matches = $null + $Matches = $null; # Body - $RestMessage -match '(\{(.*\n)*}|\{.*\})' | Out-Null + $RestMessage -match '(\{(.*\n)*}|\{.*\})' | Out-Null; $Request.Add('Body', $Matches[1]); return $Request; From 9b947dc0698928757a9570d8b5ff5ca1e0a00912 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:07:34 +0100 Subject: [PATCH 07/46] Adds additional eventlog write functions --- lib/core/logging/Write-IcingaDebugMessage.psm1 | 4 ++++ lib/core/logging/Write-IcingaErrorMessage.psm1 | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 lib/core/logging/Write-IcingaErrorMessage.psm1 diff --git a/lib/core/logging/Write-IcingaDebugMessage.psm1 b/lib/core/logging/Write-IcingaDebugMessage.psm1 index 473d129..4ba204f 100644 --- a/lib/core/logging/Write-IcingaDebugMessage.psm1 +++ b/lib/core/logging/Write-IcingaDebugMessage.psm1 @@ -4,6 +4,10 @@ function Write-IcingaDebugMessage() [string]$Message ); + if ([string]::IsNullOrEmpty($Message)) { + return; + } + if ($global:IcingaDaemonData.DebugMode -eq $FALSE) { return; } diff --git a/lib/core/logging/Write-IcingaErrorMessage.psm1 b/lib/core/logging/Write-IcingaErrorMessage.psm1 new file mode 100644 index 0000000..a90190f --- /dev/null +++ b/lib/core/logging/Write-IcingaErrorMessage.psm1 @@ -0,0 +1,13 @@ +function Write-IcingaErrorMessage() +{ + param( + [int]$EventId = 0, + [string]$Message = $null + ); + + if ($EventId -eq 0 -Or [string]::IsNullOrEmpty($Message)) { + return; + } + + Write-EventLog -LogName Application -Source 'Icinga for Windows' -EntryType Error -EventId $EventId -Message $Message; +} From df7be4a317d5c10275e7e87fe53cbfb493225171 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:09:50 +0100 Subject: [PATCH 08/46] Add debug output for starting new thread instances --- lib/core/thread/New-IcingaThreadInstance.psm1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/core/thread/New-IcingaThreadInstance.psm1 b/lib/core/thread/New-IcingaThreadInstance.psm1 index 6a2fa25..7666271 100644 --- a/lib/core/thread/New-IcingaThreadInstance.psm1 +++ b/lib/core/thread/New-IcingaThreadInstance.psm1 @@ -12,6 +12,15 @@ function New-IcingaThreadInstance() $Name = New-IcingaThreadHash -ShellScript $ScriptBlock -Arguments $Arguments; } + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Creating new thread instance {0}{1}Arguments:{1}{2}', + $Name, + "`r`n", + ($Arguments | Out-String) + ) + ); + $Shell = [PowerShell]::Create(); $Shell.RunspacePool = $ThreadPool; [void]$Shell.AddScript($ScriptBlock); From 9221cb050da00eae9e53533690d7c427d793171d Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:10:41 +0100 Subject: [PATCH 09/46] Adds function to fetch local Icinga 2 Agent host certificate --- .../Get-IcingaAgentHostCertificate.psm1 | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 diff --git a/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 b/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 new file mode 100644 index 0000000..75b1aa6 --- /dev/null +++ b/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 @@ -0,0 +1,27 @@ +function Get-IcingaAgentHostCertificate() +{ + # Default for Icinga 2.8.0 and above + [string]$CertDirectory = (Join-Path -Path $Env:ProgramData -ChildPath 'icinga2\var\lib\icinga2\certs\*'); + $FolderContent = Get-ChildItem -Path $CertDirectory -Filter '*.crt' -Exclude 'ca.crt'; + $Hostname = Get-IcingaHostname -LowerCase $TRUE; + $CertPath = $null; + + foreach ($certFile in $FolderContent) { + if ($certFile.Name.Contains($Hostname)) { + $CertPath = $certFile.FullName; + break; + } + } + + if ([string]::IsNullOrEmpty($CertPath)) { + return $null; + } + + $Certificate = New-Object Security.Cryptography.X509Certificates.X509Certificate2 $CertPath; + + return @{ + 'CertFile' = $CertPath; + 'Subject' = $Certificate.SubjectName; + 'Thumbprint' = $Certificate.Thumbprint; + }; +} From d97bc8aeb809c64c076752baad834cfbbe736763 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:11:24 +0100 Subject: [PATCH 10/46] Moves Read REST message function into proper module folder --- lib/{core/tools => web}/Read-IcingaRESTMessage.psm1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{core/tools => web}/Read-IcingaRESTMessage.psm1 (100%) diff --git a/lib/core/tools/Read-IcingaRESTMessage.psm1 b/lib/web/Read-IcingaRESTMessage.psm1 similarity index 100% rename from lib/core/tools/Read-IcingaRESTMessage.psm1 rename to lib/web/Read-IcingaRESTMessage.psm1 From 7361fd801bd9dec125a03a02f4dafd8ae5fd59f5 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:12:31 +0100 Subject: [PATCH 11/46] Adds function to securely fetch remote endpoint address from clients --- lib/web/Close-IcingaTCPConnection.psm1 | 2 +- lib/web/Get-IcingaTCPClientRemoteEndpoint.psm1 | 12 ++++++++++++ lib/web/New-IcingaTCPClient.psm1 | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 lib/web/Get-IcingaTCPClientRemoteEndpoint.psm1 diff --git a/lib/web/Close-IcingaTCPConnection.psm1 b/lib/web/Close-IcingaTCPConnection.psm1 index 81388ae..86beeae 100644 --- a/lib/web/Close-IcingaTCPConnection.psm1 +++ b/lib/web/Close-IcingaTCPConnection.psm1 @@ -11,7 +11,7 @@ function Close-IcingaTCPConnection() Write-IcingaDebugMessage -Message ( [string]::Format( 'Closing client connection for endpoint {0}', - $Client.Client.RemoteEndPoint + (Get-IcingaTCPClientRemoteEndpoint -Client $Client) ) ); diff --git a/lib/web/Get-IcingaTCPClientRemoteEndpoint.psm1 b/lib/web/Get-IcingaTCPClientRemoteEndpoint.psm1 new file mode 100644 index 0000000..28f3215 --- /dev/null +++ b/lib/web/Get-IcingaTCPClientRemoteEndpoint.psm1 @@ -0,0 +1,12 @@ +function Get-IcingaTCPClientRemoteEndpoint() +{ + param( + [System.Net.Sockets.TcpClient]$Client = $null + ); + + if ($null -eq $Client) { + return 'unknown'; + } + + return $Client.Client.RemoteEndPoint; +} diff --git a/lib/web/New-IcingaTCPClient.psm1 b/lib/web/New-IcingaTCPClient.psm1 index e3256ec..607ca1b 100644 --- a/lib/web/New-IcingaTCPClient.psm1 +++ b/lib/web/New-IcingaTCPClient.psm1 @@ -13,7 +13,7 @@ function New-IcingaTCPClient() Write-IcingaDebugMessage -Message ( [string]::Format( 'New incoming client connection for endpoint {0}', - $Client.Client.RemoteEndPoint + (Get-IcingaTCPClientRemoteEndpoint -Client $Client) ) ); From 8d945f3759b27822ef081e61f95c5208d1b17984 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:13:28 +0100 Subject: [PATCH 12/46] Fixes client initialising for SSLStream and possible crash reason --- lib/web/New-IcingaSSLStream.psm1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/web/New-IcingaSSLStream.psm1 b/lib/web/New-IcingaSSLStream.psm1 index 6ffc693..556ac12 100644 --- a/lib/web/New-IcingaSSLStream.psm1 +++ b/lib/web/New-IcingaSSLStream.psm1 @@ -1,10 +1,14 @@ function New-IcingaSSLStream() { param( - [System.Net.Sockets.TcpClient]$Client = @{}, + [System.Net.Sockets.TcpClient]$Client = $null, [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate = $null ); + if ($null -eq $Client) { + return $null; + } + $SSLStream = New-Object System.Net.Security.SslStream($Client.GetStream(), $false) $SSLStream.AuthenticateAsServer($Certificate, $false, [System.Security.Authentication.SslProtocols]::Tls12, $true) | Out-Null; From 467f366c7ca5ec0ecf8703188bbf16eb9b2b2d6e Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:14:06 +0100 Subject: [PATCH 13/46] Adds function to securely read header values from our requests --- lib/web/Get-IcingaRESTHeaderValue.psm1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/web/Get-IcingaRESTHeaderValue.psm1 diff --git a/lib/web/Get-IcingaRESTHeaderValue.psm1 b/lib/web/Get-IcingaRESTHeaderValue.psm1 new file mode 100644 index 0000000..4eb9caa --- /dev/null +++ b/lib/web/Get-IcingaRESTHeaderValue.psm1 @@ -0,0 +1,17 @@ +function Get-IcingaRESTHeaderValue() +{ + param( + [hashtable]$Request = @{}, + [string]$Header = $null + ); + + if ($null -eq $Request -or [string]::IsNullOrEmpty($Header) -Or $Request.Count -eq 0) { + return $null; + } + + if ($Request.Header.ContainsKey($Header) -eq $FALSE) { + return $null + } + + return $Request.Header[$Header]; +} From cf0c3e5602250e4c46e568c30b3444b590590f12 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:14:41 +0100 Subject: [PATCH 14/46] Adds secure function for initialising client SSL stream reader --- lib/web/Open-IcingaTCPClientConnection.psm1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/web/Open-IcingaTCPClientConnection.psm1 diff --git a/lib/web/Open-IcingaTCPClientConnection.psm1 b/lib/web/Open-IcingaTCPClientConnection.psm1 new file mode 100644 index 0000000..ade1bf2 --- /dev/null +++ b/lib/web/Open-IcingaTCPClientConnection.psm1 @@ -0,0 +1,18 @@ +function Open-IcingaTCPClientConnection() +{ + param( + [System.Net.Sockets.TcpClient]$Client = $null, + [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate = $null + ); + + if ($null -eq $Client -Or $null -eq $Certificate) { + return $null; + } + + $Stream = New-IcingaSSLStream -Client $Client -Certificate $Certificate; + + return @{ + 'Client' = $Client; + 'Stream' = $Stream; + }; +} From aaf4c5faecd2d02a2e85a8588c60d4dd0e70362e Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:15:52 +0100 Subject: [PATCH 15/46] Adds function to fetch SSL cert for sockets for various scenarios --- lib/web/Get-IcingaSSLCertForSocket.psm1 | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/web/Get-IcingaSSLCertForSocket.psm1 diff --git a/lib/web/Get-IcingaSSLCertForSocket.psm1 b/lib/web/Get-IcingaSSLCertForSocket.psm1 new file mode 100644 index 0000000..5626928 --- /dev/null +++ b/lib/web/Get-IcingaSSLCertForSocket.psm1 @@ -0,0 +1,47 @@ +function Get-IcingaSSLCertForSocket() +{ + param( + [string]$CertFile = $null, + [string]$CertThumbprint = $null + ); + + # At first check if we assigned a cert file to use directly and check + # if it is there and either import a PFX or use our convert function + # to get a proper certificate + if ([string]::IsNullOrEmpty($CertFile) -eq $FALSE) { + if ((Test-Path $CertFile)) { + $FileType = Get-Item -Path $CertFile; + if ($FileType -eq '.pfx') { + return (New-Object Security.Cryptography.X509Certificates.X509Certificate2 $CertFile); + } else { + return ConvertTo-IcingaX509Certificate -CertFile $CertFile; + } + } + } + + # We could also have assigned a Thumbprint to use from the + # Windows cert store. Try to look it up an return it if + # it is found + if ([string]::IsNullOrEmpty($CertThumbprint) -eq $FALSE) { + $Certificates = Get-ChildItem -Path 'cert:\*' -Recurse ` + -Include $CertThumbprint ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue; + + if ($Certificates.Count -ne 0) { + return $Certificates[0]; + } + } + + # If no cert file or thumbprint was specified or simpy as fallback, + # we should use the Icinga 2 Agent certificates + $AgentCertificate = Get-IcingaAgentHostCertificate; + + # If Agent is not installed or certificates were not found, + # simply return null + if ($null -eq $AgentCertificate) { + return $null; + } + + return (ConvertTo-IcingaX509Certificate -CertFile $AgentCertificate.CertFile); +} From 691faadf8735d860b9aa0fbe8fa3b59f4685db57 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 24 Mar 2020 20:42:11 +0100 Subject: [PATCH 16/46] Fixes code styling, unused variables and function shortnames --- lib/web/Read-IcingaRESTMessage.psm1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/web/Read-IcingaRESTMessage.psm1 b/lib/web/Read-IcingaRESTMessage.psm1 index ffed571..496c4df 100644 --- a/lib/web/Read-IcingaRESTMessage.psm1 +++ b/lib/web/Read-IcingaRESTMessage.psm1 @@ -15,8 +15,7 @@ function Read-IcingaRESTMessage() #Path $PathMatch = $Matches[3]; - $PathMatch -match '(.+)\?(.*)' | Out-Null - $Path = $Matches[1]; + $PathMatch -match '(.+)\?(.*)' | Out-Null; $Arguments = $Matches[2]; $Request.RequestPath.Add('FullPath', $Matches[1]); $Request.RequestPath.Add('PathArray', $Matches[1].TrimStart('/').Split('/')); @@ -26,14 +25,14 @@ function Read-IcingaRESTMessage() # Arguments $ArgumentsSplit = $Arguments.Split('&'); $ArgumentsSplit+='\\\\\\\\\\\\=FIN'; - foreach ( $Argument in $ArgumentsSplit | sort -descending) { + foreach ( $Argument in $ArgumentsSplit | Sort-Object -descending) { $Argument -match '(.+)=(.+)' | Out-Null; If (($Matches[1] -ne $Current) -And ($NULL -ne $Current)) { $Request.RequestArguments.Add( $Current, $ArgumentContent ); [array]$ArgumentContent = $null; } $Current = $Matches[1]; - [array]$ArgumentContent+=($Matches[2]); + [array]$ArgumentContent += ($Matches[2]); } # Header From 9831e9319ceca7e0e9e323c1c6d337086d0cd7d0 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 07:48:55 +0100 Subject: [PATCH 17/46] Fixes certificate subject name fetching --- .../icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 b/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 index 75b1aa6..bbf1baf 100644 --- a/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 +++ b/lib/core/icingaagent/getters/Get-IcingaAgentHostCertificate.psm1 @@ -21,7 +21,7 @@ function Get-IcingaAgentHostCertificate() return @{ 'CertFile' = $CertPath; - 'Subject' = $Certificate.SubjectName; + 'Subject' = $Certificate.Subject; 'Thumbprint' = $Certificate.Thumbprint; }; } From a78ac014be5639abcebf2e6cf5676e0f2f2a5afa Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 08:35:29 +0100 Subject: [PATCH 18/46] Adds improved handling for error messages and later doc on website --- lib/core/logging/Icinga_EventLog_Enums.psm1 | 14 +++++++++++ .../logging/Write-IcingaEventMessage.psm1 | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 lib/core/logging/Icinga_EventLog_Enums.psm1 create mode 100644 lib/core/logging/Write-IcingaEventMessage.psm1 diff --git a/lib/core/logging/Icinga_EventLog_Enums.psm1 b/lib/core/logging/Icinga_EventLog_Enums.psm1 new file mode 100644 index 0000000..30cd05b --- /dev/null +++ b/lib/core/logging/Icinga_EventLog_Enums.psm1 @@ -0,0 +1,14 @@ +<# + # This script will provide 'Enums' we can use for proper + # error handling and to provide more detailed descriptions + # + # Example usage: + # $IcingaEventLogEnums[2000] + #> + [hashtable]$IcingaEventLogEnums += @{ + 'Framework' = @{ + # TODO: Add event log messages + } +}; + +Export-ModuleMember -Variable @( 'IcingaEventLogEnums' ); diff --git a/lib/core/logging/Write-IcingaEventMessage.psm1 b/lib/core/logging/Write-IcingaEventMessage.psm1 new file mode 100644 index 0000000..bfb4bb5 --- /dev/null +++ b/lib/core/logging/Write-IcingaEventMessage.psm1 @@ -0,0 +1,24 @@ +function Write-IcingaEventMessage() +{ + param( + [int]$EventId = 0, + [string]$Namespace = $null + ); + + if ($EventId -eq 0 -Or $null -eq $Namespace) { + return; + } + + $EntryType = $IcingaEventLogEnums[$Namespace][$EventId].EntryType; + $Message = $IcingaEventLogEnums[$Namespace][$EventId].Message; + + if ($null -eq $EntryType -Or $null -eq $Message) { + return; + } + + Write-EventLog -LogName Application ` + -Source 'Icinga for Windows' ` + -EntryType $EntryType ` + -EventId $EventId ` + -Message $Message; +} From 6de97f439f142c5a3dcc9a60429aa13a387deb19 Mon Sep 17 00:00:00 2001 From: Alexander Stoll <45196568+Crited@users.noreply.github.com> Date: Wed, 25 Mar 2020 15:33:32 +0100 Subject: [PATCH 19/46] Change Regex to support more input variants References #57 --- lib/web/Read-IcingaRESTMessage.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web/Read-IcingaRESTMessage.psm1 b/lib/web/Read-IcingaRESTMessage.psm1 index 496c4df..3a0f0c6 100644 --- a/lib/web/Read-IcingaRESTMessage.psm1 +++ b/lib/web/Read-IcingaRESTMessage.psm1 @@ -15,8 +15,8 @@ function Read-IcingaRESTMessage() #Path $PathMatch = $Matches[3]; - $PathMatch -match '(.+)\?(.*)' | Out-Null; - $Arguments = $Matches[2]; + $PathMatch -match '((\/[^\/\?]+)*)\??([^\/]*)' | Out-Null; + $Arguments = $Matches[3]; $Request.RequestPath.Add('FullPath', $Matches[1]); $Request.RequestPath.Add('PathArray', $Matches[1].TrimStart('/').Split('/')); From e56861d858178a16234aa1b2235b6a0228532dd8 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 17:48:49 +0100 Subject: [PATCH 20/46] Adds generic support for sending TCP client and REST network messages --- lib/core/tools/New-IcingaNewLine.psm1 | 4 ++ lib/web/Icinga_HTTPResponse_Enums.psm1 | 36 ++++++++++++ lib/web/New-IcingaTCPClientRESTMessage.psm1 | 63 +++++++++++++++++++++ lib/web/Send-IcingaTCPClientMessage.psm1 | 14 +++++ 4 files changed, 117 insertions(+) create mode 100644 lib/core/tools/New-IcingaNewLine.psm1 create mode 100644 lib/web/Icinga_HTTPResponse_Enums.psm1 create mode 100644 lib/web/New-IcingaTCPClientRESTMessage.psm1 create mode 100644 lib/web/Send-IcingaTCPClientMessage.psm1 diff --git a/lib/core/tools/New-IcingaNewLine.psm1 b/lib/core/tools/New-IcingaNewLine.psm1 new file mode 100644 index 0000000..0f8d9b6 --- /dev/null +++ b/lib/core/tools/New-IcingaNewLine.psm1 @@ -0,0 +1,4 @@ +function New-IcingaNewLine() +{ + return "`r`n"; +} diff --git a/lib/web/Icinga_HTTPResponse_Enums.psm1 b/lib/web/Icinga_HTTPResponse_Enums.psm1 new file mode 100644 index 0000000..d6fd01e --- /dev/null +++ b/lib/web/Icinga_HTTPResponse_Enums.psm1 @@ -0,0 +1,36 @@ +<# + # This script will provide 'Enums' we can use within our module to + # easier access constants and to maintain a better overview of the + # entire components + #> +[hashtable]$HTTPResponseCode = @{ + 200 = 'Ok'; + 400 = 'Bad Request'; + 401 = 'Unauthorized'; + 403 = 'Forbidden'; + 404 = 'Not Found' + 500 = 'Internal Server Error'; +}; + +[hashtable]$HTTPResponseType = @{ + 'Ok' = 200; + 'Bad Request' = 400; + 'Unauthorized' = 401; + 'Forbidden' = 403; + 'Not Found' = 404; + 'Internal Server Error' = 500; +}; + +<# + # Once we defined a new enum hashtable above, simply add it to this list + # to make it available within the entire module. + # + # Example usage: + # $IcingaHTTPEnums.HTTPResponseType.Ok + #> +[hashtable]$IcingaHTTPEnums = @{ + HTTPResponseCode = $HTTPResponseCode; + HTTPResponseType = $HTTPResponseType; +} + +Export-ModuleMember -Variable @( 'IcingaHTTPEnums' ); diff --git a/lib/web/New-IcingaTCPClientRESTMessage.psm1 b/lib/web/New-IcingaTCPClientRESTMessage.psm1 new file mode 100644 index 0000000..bce7fe8 --- /dev/null +++ b/lib/web/New-IcingaTCPClientRESTMessage.psm1 @@ -0,0 +1,63 @@ +function New-IcingaTCPClientRESTMessage() +{ + param( + [Hashtable]$Headers = $null, + $ContentBody = $null, + [int]$HTTPResponse = 200, + [switch]$BasicAuth = $FALSE + ); + + [string]$ContentLength = ''; + [string]$HTMLContent = ''; + [string]$AuthHeader = ''; + + if ($null -ne $ContentBody) { + $json = ConvertTo-Json $ContentBody -Depth 100 -Compress; + $bytes = [System.Text.Encoding]::UTF8.GetBytes($json); + $HTMLContent = [System.Text.Encoding]::UTF8.GetString($bytes); + if ($bytes.Length -gt 0) { + $ContentLength = [string]::Format( + 'Content-Length: {0}{1}', + $bytes.Length, + (New-IcingaNewLine) + ); + } + } + + if ($BasicAuth) { + $AuthHeader = [string]::Format( + 'WWW-Authenticate: Basic realm="Icinga for Windows"{0}', + (New-IcingaNewLine) + ); + } + + $ResponseMeessage = -Join( + [string]::Format( + 'HTTP/1.1 {0} {1}{2}', + $HTTPResponse, + $IcingaHTTPEnums.HTTPResponseCode[$HTTPResponse], + (New-IcingaNewLine) + ), + [string]::Format( + 'Server: {0}{1}', + (Get-IcingaHostname -LowerCase $TRUE -AutoUseFQDN $TRUE), + (New-IcingaNewLine) + ), + [string]::Format( + 'Content-Type: application/json{0}', + (New-IcingaNewLine) + ), + $AuthHeader, + $ContentLength, + (New-IcingaNewLine), + $HTMLContent + ); + + # Encode our message before sending it + $UTF8Message = [System.Text.Encoding]::UTF8.GetBytes($ResponseMeessage); + + return @{ + 'message' = $UTF8Message; + 'length' = $UTF8Message.Length; + }; +} diff --git a/lib/web/Send-IcingaTCPClientMessage.psm1 b/lib/web/Send-IcingaTCPClientMessage.psm1 new file mode 100644 index 0000000..9ee0613 --- /dev/null +++ b/lib/web/Send-IcingaTCPClientMessage.psm1 @@ -0,0 +1,14 @@ +function Send-IcingaTCPClientMessage() +{ + param( + [Hashtable]$Message = @{}, + [System.Net.Security.SslStream]$Stream = $null + ); + + if ($null -eq $Message -Or $Message.Count -eq 0 -Or $Message.length -eq 0) { + return; + } + + $Stream.Write($Message.message, 0, $Message.length); + $Stream.Flush(); +} From abb2ab1e35ea67cbee3cc008219560810df84082 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 17:50:02 +0100 Subject: [PATCH 21/46] Adds function for simple navigation through REST-Api calls --- lib/web/Get-IcingaRESTPathElement.psm1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/web/Get-IcingaRESTPathElement.psm1 diff --git a/lib/web/Get-IcingaRESTPathElement.psm1 b/lib/web/Get-IcingaRESTPathElement.psm1 new file mode 100644 index 0000000..549d3fe --- /dev/null +++ b/lib/web/Get-IcingaRESTPathElement.psm1 @@ -0,0 +1,21 @@ +function Get-IcingaRESTPathElement() +{ + param( + [Hashtable]$Request = @{}, + [int]$Index = 0 + ); + + if ($null -eq $Request -Or $Request.Count -eq 0) { + return ''; + } + + if ($Request.ContainsKey('RequestPath') -eq $FALSE) { + return ''; + } + + if (($Index + 1) -gt $Request.RequestPath.PathArray.Count) { + return ''; + } + + return $Request.RequestPath.PathArray[$Index]; +} From 935c497a7d17cff7127e1511edac3f143c96f0ef Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 17:50:56 +0100 Subject: [PATCH 22/46] Extends eventlog write function for pre-defined messages and object dump --- .../logging/Write-IcingaEventMessage.psm1 | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/core/logging/Write-IcingaEventMessage.psm1 b/lib/core/logging/Write-IcingaEventMessage.psm1 index bfb4bb5..904e793 100644 --- a/lib/core/logging/Write-IcingaEventMessage.psm1 +++ b/lib/core/logging/Write-IcingaEventMessage.psm1 @@ -2,15 +2,50 @@ function Write-IcingaEventMessage() { param( [int]$EventId = 0, - [string]$Namespace = $null + [string]$Namespace = $null, + [array]$Objects = @() ); - if ($EventId -eq 0 -Or $null -eq $Namespace) { + if ($EventId -eq 0 -Or [string]::IsNullOrEmpty($Namespace)) { return; } - $EntryType = $IcingaEventLogEnums[$Namespace][$EventId].EntryType; - $Message = $IcingaEventLogEnums[$Namespace][$EventId].Message; + [string]$EntryType = $IcingaEventLogEnums[$Namespace][$EventId].EntryType; + [string]$Message = $IcingaEventLogEnums[$Namespace][$EventId].Message; + [string]$Details = $IcingaEventLogEnums[$Namespace][$EventId].Details; + + if ([string]::IsNullOrEmpty($Details)) { + $Details = ''; + } + if ([string]::IsNullOrEmpty($Message)) { + $Message = ''; + } + + [string]$ObjectDump = ''; + + if ($Objects.Count -eq 0) { + $ObjectDump = [string]::Format( + '{0}{0}No additional object details provided.', + (New-IcingaNewLine) + ); + } + + foreach ($entry in $Objects) { + $ObjectDump = [string]::Format( + '{0}{1}', + $ObjectDump, + ($entry | Out-String) + ); + } + + [string]$EventLogMessage = [string]::Format( + '{0}{1}{1}{2}{1}{1}Object dumps if available:{1}{3}', + $Message, + (New-IcingaNewLine), + $Details, + $ObjectDump + + ); if ($null -eq $EntryType -Or $null -eq $Message) { return; @@ -20,5 +55,5 @@ function Write-IcingaEventMessage() -Source 'Icinga for Windows' ` -EntryType $EntryType ` -EventId $EventId ` - -Message $Message; + -Message $EventLogMessage; } From 2e225e1ce5c20c70a0099b91025d75e65704bd09 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 17:51:53 +0100 Subject: [PATCH 23/46] Adds support for generic call of wildcard functions for fetching multi data --- .../Invoke-IcingaNamespaceCmdlets.psm1 | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lib/core/framework/Invoke-IcingaNamespaceCmdlets.psm1 diff --git a/lib/core/framework/Invoke-IcingaNamespaceCmdlets.psm1 b/lib/core/framework/Invoke-IcingaNamespaceCmdlets.psm1 new file mode 100644 index 0000000..c4b88dd --- /dev/null +++ b/lib/core/framework/Invoke-IcingaNamespaceCmdlets.psm1 @@ -0,0 +1,30 @@ +function Invoke-IcingaNamespaceCmdlets() +{ + param ( + [string]$Command + ); + + [Hashtable]$CommandConfig = @{}; + + if ($Command.Contains('*') -eq $FALSE) { + $Command = [string]::Format('{0}*', $Command); + } + + $CommandList = Get-Command $Command; + + foreach ($Cmdlet in $CommandList) { + try { + $CmmandName = $Cmdlet.Name; + Import-Module $Cmdlet.Module.Path -WarningAction SilentlyContinue -ErrorAction Stop; + + $Content = (& $CmmandName); + Add-IcingaHashtableItem -Hashtable $CommandConfig ` + -Key $Cmdlet.Name ` + -Value $Content | Out-Null; + } catch { + # TODO: Add event log logging on exceptions + } + } + + return $CommandConfig; +} From 483356136cccae478e7df97e990f31f63c8617ea Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 17:53:18 +0100 Subject: [PATCH 24/46] Adds support for auto loading third-party module eventlog messages --- icinga-powershell-framework.psm1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index 640926c..f29560c 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -29,6 +29,15 @@ function Use-Icinga() Import-IcingaLib '\' -Init -Custom; Import-IcingaLib '\' -Init; + $EventLogMessages = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaEventLogMessages*'; + foreach ($entry in $EventLogMessages.Values) { + foreach ($event in $entry.Keys) { + Add-IcingaHashtableItem -Hashtable $global:IcingaEventLogEnums ` + -Key $event ` + -Value $entry[$event] | Out-Null; + } + } + if ($LibOnly -eq $FALSE) { Register-IcingaEventLog; From eb948d8d22ecf3cba2514574568881222a1be0dd Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 22:03:31 +0100 Subject: [PATCH 25/46] Adds support for simple setup and fetching of internal check scheduler data --- .../Get-IcingaCheckSchedulerPerfData.psm1 | 23 +++++++++++++++++++ .../Get-IcingaCheckSchedulerPluginOutput.psm1 | 23 +++++++++++++++++++ .../New-IcingaCheckSchedulerEnvironment.psm1 | 8 +++++++ 3 files changed, 54 insertions(+) create mode 100644 lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 create mode 100644 lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 create mode 100644 lib/core/framework/New-IcingaCheckSchedulerEnvironment.psm1 diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 new file mode 100644 index 0000000..8b750fa --- /dev/null +++ b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 @@ -0,0 +1,23 @@ +function Get-IcingaCheckSchedulerPerfData() +{ + if ($null -eq $IcingaDaemonData) { + return $null; + } + + if ($IcingaDaemonData.ContainsKey('IcingaThreadContent') -eq $FALSE) { + return $null; + } + + if ($IcingaDaemonData.IcingaThreadContent.ContainsKey('Scheduler') -eq $FALSE) { + return $null; + } + + if ($IcingaDaemonData.IcingaThreadContent.Scheduler.ContainsKey('PluginPerfData') -eq $FALSE) { + return $null; + } + + $PerfData = $IcingaDaemonData.IcingaThreadContent.Scheduler.PluginPerfData; + $IcingaDaemonData.IcingaThreadContent.Scheduler.PluginPerfData = @(); + + return $PerfData; +} diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 new file mode 100644 index 0000000..6f2ad89 --- /dev/null +++ b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 @@ -0,0 +1,23 @@ +function Get-IcingaCheckSchedulerPluginOutput() +{ + if ($null -eq $IcingaDaemonData) { + return $null; + } + + if ($IcingaDaemonData.ContainsKey('IcingaThreadContent') -eq $FALSE) { + return $null; + } + + if ($IcingaDaemonData.IcingaThreadContent.ContainsKey('Scheduler') -eq $FALSE) { + return $null; + } + + if ($IcingaDaemonData.IcingaThreadContent.Scheduler.ContainsKey('PluginCache') -eq $FALSE) { + return $null; + } + + $CheckResult = [string]::Join("`r`n", $IcingaDaemonData.IcingaThreadContent.Scheduler.PluginCache); + $IcingaDaemonData.IcingaThreadContent.Scheduler.PluginCache = @(); + + return $CheckResult; +} diff --git a/lib/core/framework/New-IcingaCheckSchedulerEnvironment.psm1 b/lib/core/framework/New-IcingaCheckSchedulerEnvironment.psm1 new file mode 100644 index 0000000..bc737ec --- /dev/null +++ b/lib/core/framework/New-IcingaCheckSchedulerEnvironment.psm1 @@ -0,0 +1,8 @@ +function New-IcingaCheckSchedulerEnvironment() +{ + $IcingaDaemonData.IcingaThreadContent.Add('Scheduler', @{ }); + if ($IcingaDaemonData.IcingaThreadContent['Scheduler'].ContainsKey('PluginCache') -eq $FALSE) { + $IcingaDaemonData.IcingaThreadContent['Scheduler'].Add('PluginCache', @()); + $IcingaDaemonData.IcingaThreadContent['Scheduler'].Add('PluginPerfData', @()); + } +} From 7a732de5f409477ba8ebb5c41b17b8b539fdce32 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 22:12:17 +0100 Subject: [PATCH 26/46] Fixes debug messages being written while no debug option was defined --- lib/core/logging/Write-IcingaDebugMessage.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/logging/Write-IcingaDebugMessage.psm1 b/lib/core/logging/Write-IcingaDebugMessage.psm1 index 4ba204f..187765c 100644 --- a/lib/core/logging/Write-IcingaDebugMessage.psm1 +++ b/lib/core/logging/Write-IcingaDebugMessage.psm1 @@ -8,7 +8,7 @@ function Write-IcingaDebugMessage() return; } - if ($global:IcingaDaemonData.DebugMode -eq $FALSE) { + if ($null -eq $global:IcingaDaemonData -Or $global:IcingaDaemonData.DebugMode -eq $FALSE) { return; } From 90ef20a7e5deeae82177adad24878ee745570f33 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Wed, 25 Mar 2020 22:28:54 +0100 Subject: [PATCH 27/46] Fixes debug/daemon mode while loading libs only --- icinga-powershell-framework.psm1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index f29560c..e0e52e7 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -53,6 +53,18 @@ function Use-Icinga() 'DebugMode' = $DebugMode; } ); + } else { + # This will fix the debug mode in case we are only using Libs + # without any other variable content and daemon handling + if ($null -eq $global:IcingaDaemonData) { + $global:IcingaDaemonData = [hashtable]::Synchronized(@{}); + } + if ($global:IcingaDaemonData.ContainsKey('DebugMode') -eq $FALSE) { + $global:IcingaDaemonData.DebugMode = $DebugMode; + } + if ($global:IcingaDaemonData.ContainsKey('FrameworkRunningAsDaemon') -eq $FALSE) { + $global:IcingaDaemonData.FrameworkRunningAsDaemon = $Daemon; + } } } From 8a19899fff5ba04364484b0d073e297ed205f51c Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 07:32:20 +0100 Subject: [PATCH 28/46] Fixes x509 cert conversion to only delete temp files not output files --- lib/web/ConvertTo-IcingaX509Certificate.psm1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/web/ConvertTo-IcingaX509Certificate.psm1 b/lib/web/ConvertTo-IcingaX509Certificate.psm1 index 2bba530..3580ce2 100644 --- a/lib/web/ConvertTo-IcingaX509Certificate.psm1 +++ b/lib/web/ConvertTo-IcingaX509Certificate.psm1 @@ -41,7 +41,9 @@ function ConvertTo-IcingaX509Certificate() # Now load the actual certificate from the path $Certificate = New-Object Security.Cryptography.X509Certificates.X509Certificate2 $TargetFile; # Delete the PFX-Certificate which will be present after certutil merge - Remove-Item $TargetFile -Force -ErrorAction SilentlyContinue; + if ($TempFile) { + Remove-Item $TargetFile -Force -ErrorAction SilentlyContinue; + } # Return the certificate return $Certificate From c6dc0820813d33f9240ddb65d4615c58ebe974dd Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 07:32:57 +0100 Subject: [PATCH 29/46] Fixes cert file output file to apply --- lib/web/ConvertTo-IcingaX509Certificate.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/ConvertTo-IcingaX509Certificate.psm1 b/lib/web/ConvertTo-IcingaX509Certificate.psm1 index 3580ce2..3ca8038 100644 --- a/lib/web/ConvertTo-IcingaX509Certificate.psm1 +++ b/lib/web/ConvertTo-IcingaX509Certificate.psm1 @@ -9,7 +9,7 @@ function ConvertTo-IcingaX509Certificate() # Use an empty password for converted certificates $Password = $null; # Use a target file to specify if we use temp files or not - $TargetFile = $null; + $TargetFile = $OutFile; # Temp Cert [bool]$TempFile = $FALSE; From 30a7d6f25738fdd3c6cd5d5cb6efa03f20126d96 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 07:33:28 +0100 Subject: [PATCH 30/46] Adds exceptions in case source Cert file is missing as argument or invalid --- lib/web/ConvertTo-IcingaX509Certificate.psm1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/web/ConvertTo-IcingaX509Certificate.psm1 b/lib/web/ConvertTo-IcingaX509Certificate.psm1 index 3ca8038..d1c4e44 100644 --- a/lib/web/ConvertTo-IcingaX509Certificate.psm1 +++ b/lib/web/ConvertTo-IcingaX509Certificate.psm1 @@ -6,6 +6,14 @@ function ConvertTo-IcingaX509Certificate() [switch]$Force = $FALSE ); + if ([string]::IsNullOrEmpty($CertFile)) { + throw 'Please specify a valid path to an existing certificate (.cer, .pem, .cert)'; + } + + if ((Test-Path $CertFile) -eq $FALSE) { + throw 'The provided path to your certificate was not valid'; + } + # Use an empty password for converted certificates $Password = $null; # Use a target file to specify if we use temp files or not From d30970b3a9a36d1f9f2a01532e49451e3c51b488 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 07:34:10 +0100 Subject: [PATCH 31/46] Improves certutil output by writing it into eventlog on debug mode --- lib/web/ConvertTo-IcingaX509Certificate.psm1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/web/ConvertTo-IcingaX509Certificate.psm1 b/lib/web/ConvertTo-IcingaX509Certificate.psm1 index d1c4e44..b3a4915 100644 --- a/lib/web/ConvertTo-IcingaX509Certificate.psm1 +++ b/lib/web/ConvertTo-IcingaX509Certificate.psm1 @@ -37,9 +37,17 @@ function ConvertTo-IcingaX509Certificate() # it is a temp file or we force its creation if (-Not (Test-Path $TargetFile) -Or $TempFile -Or $Force) { Write-Output "$Password - $Password" | certutil -mergepfx "$CertFile" "$TargetFile" | Out-Null; + $Password" | certutil -mergepfx "$CertFile" "$TargetFile" | Set-Variable -Name 'CertUtilOutput'; } + Write-IcingaDebugMessage -Message ( + [string]::Format( + 'Certutil merge request has been completed. Certutil message:{0}{0}{1}', + (New-IcingaNewLine), + $CertUtilOutput + ) + ); + # If no target file exists afterwards (a valid PFX certificate) # then throw an exception if (-Not (Test-Path $TargetFile)) { From d62e566fea5bd8101c8576895a3d605e654b3ad7 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 18:08:48 +0100 Subject: [PATCH 32/46] Adds Cmdlets to enable/disable untrusted certificates for rest endpoints --- ...-IcingaUntrustedCertificateValidation.psm1 | 14 ++++++++++ ...-IcingaUntrustedCertificateValidation.psm1 | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 lib/web/Disable-IcingaUntrustedCertificateValidation.psm1 create mode 100644 lib/web/Enable-IcingaUntrustedCertificateValidation.psm1 diff --git a/lib/web/Disable-IcingaUntrustedCertificateValidation.psm1 b/lib/web/Disable-IcingaUntrustedCertificateValidation.psm1 new file mode 100644 index 0000000..386d9d5 --- /dev/null +++ b/lib/web/Disable-IcingaUntrustedCertificateValidation.psm1 @@ -0,0 +1,14 @@ +function Disable-IcingaUntrustedCertificateValidation() +{ + try { + [System.Net.ServicePointManager]::CertificatePolicy = $null; + + Write-Host 'Successfully disabled untrusted certificate validation for this shell instance'; + } catch { + Write-Host ( + [string]::Format( + 'Failed to disable untrusted certificate policy: {0}', $_.Exception.Message + ) + ); + } +} diff --git a/lib/web/Enable-IcingaUntrustedCertificateValidation.psm1 b/lib/web/Enable-IcingaUntrustedCertificateValidation.psm1 new file mode 100644 index 0000000..7fd9f54 --- /dev/null +++ b/lib/web/Enable-IcingaUntrustedCertificateValidation.psm1 @@ -0,0 +1,27 @@ +function Enable-IcingaUntrustedCertificateValidation() +{ + try { + # There is no other way as to use C# for this specific + # case to configure the certificate validation check + add-type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + + public class IcingaUntrustedCertificateValidation : ICertificatePolicy { + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; + } + } +"@ + + [System.Net.ServicePointManager]::CertificatePolicy = New-Object IcingaUntrustedCertificateValidation; + + Write-Host 'Successfully enabled untrusted certificate validation for this shell instance'; + } catch { + Write-Host ( + [string]::Format( + 'Failed to enable untrusted certificate policy: {0}', $_.Exception.Message + ) + ); + } +} From 7dce37717af07f123a80a4717313f2032fd940f5 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 26 Mar 2020 19:43:37 +0100 Subject: [PATCH 33/46] Adds generic PowerShell component installer and improved usability --- .../Get-IcingaPowerShellModuleArchive.psm1 | 48 +++++++++-- .../Install-IcingaFrameworkComponent.psm1 | 83 +++++++++++++++++++ .../Install-IcingaFrameworkPlugins.psm1 | 50 ++--------- 3 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 lib/core/framework/Install-IcingaFrameworkComponent.psm1 diff --git a/lib/core/framework/Get-IcingaPowerShellModuleArchive.psm1 b/lib/core/framework/Get-IcingaPowerShellModuleArchive.psm1 index 4f6025d..36a8afc 100644 --- a/lib/core/framework/Get-IcingaPowerShellModuleArchive.psm1 +++ b/lib/core/framework/Get-IcingaPowerShellModuleArchive.psm1 @@ -3,23 +3,44 @@ function Get-IcingaPowerShellModuleArchive() param( [string]$DownloadUrl = '', [string]$ModuleName = '', - [string]$Repository = '' + [string]$Repository = '', + [string]$GitHubUser = 'Icinga', + [bool]$Stable = $FALSE, + [bool]$Snapshot = $FALSE, + [bool]$DryRun = $FALSE ); $ProgressPreference = "SilentlyContinue"; $Tag = 'master'; + [bool]$SkipRepo = $FALSE; + + if ($Stable -Or $Snapshot) { + $SkipRepo = $TRUE; + } + # Fix TLS errors while connecting to GitHub with old PowerShell versions [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11"; if ([string]::IsNullOrEmpty($DownloadUrl)) { - if ((Get-IcingaAgentInstallerAnswerInput -Prompt ([string]::Format('Do you provide a custom repository for "{0}"?', $ModuleName)) -Default 'n').result -eq 1) { - $branch = (Get-IcingaAgentInstallerAnswerInput -Prompt 'Which version to you want to install? (snapshot/stable)' -Default 'v' -DefaultInput 'stable').answer - if ($branch.ToLower() -eq 'snapshot') { - $DownloadUrl = [string]::Format('https://github.com/Icinga/{0}/archive/master.zip', $Repository); + if ($SkipRepo -Or (Get-IcingaAgentInstallerAnswerInput -Prompt ([string]::Format('Do you provide a custom repository for "{0}"?', $ModuleName)) -Default 'n').result -eq 1) { + if ($Stable -eq $FALSE -And $Snapshot -eq $FALSE) { + $branch = (Get-IcingaAgentInstallerAnswerInput -Prompt 'Which version to you want to install? (snapshot/stable)' -Default 'v' -DefaultInput 'stable').answer; + } elseif ($Stable) { + $branch = 'stable'; } else { - $LatestRelease = (Invoke-WebRequest -Uri ([string]::Format('https://github.com/Icinga/{0}/releases/latest', $Repository)) -UseBasicParsing).BaseResponse.ResponseUri.AbsoluteUri; - $DownloadUrl = $LatestRelease.Replace('/releases/tag/', '/archive/'); - $Tag = $DownloadUrl.Split('/')[-1]; + $branch = 'snapshot' + } + if ($branch.ToLower() -eq 'snapshot') { + $DownloadUrl = [string]::Format('https://github.com/{0}/{1}/archive/master.zip', $GitHubUser, $Repository); + } else { + try { + $LatestRelease = (Invoke-WebRequest -Uri ([string]::Format('https://github.com/{0}/{1}/releases/latest', $GitHubUser, $Repository)) -UseBasicParsing).BaseResponse.ResponseUri.AbsoluteUri; + $DownloadUrl = $LatestRelease.Replace('/releases/tag/', '/archive/'); + $Tag = $DownloadUrl.Split('/')[-1]; + } catch { + Write-Host 'Failed to fetch latest release from GitHub. Either the module or the GitHub account do not exist'; + } + $DownloadUrl = [string]::Format('{0}/{1}.zip', $DownloadUrl, $Tag); $CurrentVersion = Get-IcingaPowerShellModuleVersion $Repository; @@ -41,6 +62,17 @@ function Get-IcingaPowerShellModuleArchive() } } + if ($DryRun) { + return @{ + 'DownloadUrl' = $DownloadUrl; + 'Version' = $Tag; + 'Directory' = ''; + 'Archive' = ''; + 'ModuleRoot' = (Get-IcingaFrameworkRootPath); + 'Installed' = $FALSE; + }; + } + try { $DownloadDirectory = New-IcingaTemporaryDirectory; $DownloadDestination = (Join-Path -Path $DownloadDirectory -ChildPath ([string]::Format('{0}.zip', $Repository))); diff --git a/lib/core/framework/Install-IcingaFrameworkComponent.psm1 b/lib/core/framework/Install-IcingaFrameworkComponent.psm1 new file mode 100644 index 0000000..2a5b0c6 --- /dev/null +++ b/lib/core/framework/Install-IcingaFrameworkComponent.psm1 @@ -0,0 +1,83 @@ +function Install-IcingaFrameworkComponent() +{ + param( + [string]$Name, + [string]$GitHubUser = 'Icinga', + [string]$Url, + [switch]$Stable = $FALSE, + [switch]$Snapshot = $FALSE, + [switch]$DryRun = $FALSE + ); + + if ([string]::IsNullOrEmpty($Name)) { + throw 'Please specify a component name to install from a GitHub/Local space'; + } + + $TextInfo = (Get-Culture).TextInfo; + $ComponentName = $TextInfo.ToTitleCase($Name); + $RepositoryName = [string]::Format('icinga-powershell-{0}', $Name); + $Archive = Get-IcingaPowerShellModuleArchive ` + -DownloadUrl $Url ` + -GitHubUser $GitHubUser ` + -ModuleName ( + [string]::Format( + 'Icinga Component {0}', $ComponentName + ) + ) ` + -Repository $RepositoryName ` + -Stable $Stable ` + -Snapshot $Snapshot ` + -DryRun $DryRun; + + if ($Archive.Installed -eq $FALSE -Or $DryRun) { + return @{ + 'RepoUrl' = $Archive.DownloadUrl + }; + } + + Write-Host ([string]::Format('Installing module into "{0}"', ($Archive.Directory))); + Expand-IcingaZipArchive -Path $Archive.Archive -Destination $Archive.Directory | Out-Null; + + $FolderContent = Get-ChildItem -Path $Archive.Directory; + $ModuleContent = $Archive.Directory; + + foreach ($entry in $FolderContent) { + if ($entry -like ([string]::Format('{0}*', $RepositoryName))) { + $ModuleContent = Join-Path -Path $ModuleContent -ChildPath $entry; + break; + } + } + + Write-Host ([string]::Format('Using content of folder "{0}" for updates', $ModuleContent)); + + $PluginDirectory = (Join-Path -Path $Archive.ModuleRoot -ChildPath $RepositoryName); + + if ((Test-Path $PluginDirectory) -eq $FALSE) { + Write-Host ([string]::Format('{0} Module Directory "{1}" is not present. Creating Directory', $ComponentName, $PluginDirectory)); + New-Item -Path $PluginDirectory -ItemType Directory | Out-Null; + } + + Write-Host ([string]::Format('Copying files to {0}', $ComponentName)); + Copy-ItemSecure -Path (Join-Path -Path $ModuleContent -ChildPath '/*') -Destination $PluginDirectory -Recurse -Force | Out-Null; + + Write-Host 'Cleaning temporary content'; + Start-Sleep -Seconds 1; + Remove-ItemSecure -Path $Archive.Directory -Recurse -Force | Out-Null; + + Unblock-IcingaPowerShellFiles -Path $PluginDirectory; + + # In case the plugins are not installed before, load the framework again to + # include the plugins + Use-Icinga; + + # Unload the module if it was loaded before + Remove-Module $RepositoryName -Force -ErrorAction SilentlyContinue; + # Now import the module + Import-Module $RepositoryName; + + Write-Host ([string]::Format('Icinga component {0} update has been completed. Please start a new PowerShell to apply it', $ComponentName)); + + return @{ + 'RepoUrl' = $Archive.DownloadUrl + }; +} diff --git a/lib/core/framework/Install-IcingaFrameworkPlugins.psm1 b/lib/core/framework/Install-IcingaFrameworkPlugins.psm1 index 7d11b40..124d0ea 100644 --- a/lib/core/framework/Install-IcingaFrameworkPlugins.psm1 +++ b/lib/core/framework/Install-IcingaFrameworkPlugins.psm1 @@ -4,52 +4,12 @@ function Install-IcingaFrameworkPlugins() [string]$PluginsUrl ); - $RepositoryName = 'icinga-powershell-plugins'; - $Archive = Get-IcingaPowerShellModuleArchive -DownloadUrl $PluginsUrl -ModuleName 'Icinga Plugins' -Repository $RepositoryName; - - if ($Archive.Installed -eq $FALSE) { - return @{ - 'PluginUrl' = $Archive.DownloadUrl - }; - } - - Write-Host ([string]::Format('Installing module into "{0}"', ($Archive.Directory))); - Expand-IcingaZipArchive -Path $Archive.Archive -Destination $Archive.Directory | Out-Null; - - $FolderContent = Get-ChildItem -Path $Archive.Directory; - $ModuleContent = $Archive.Directory; - - foreach ($entry in $FolderContent) { - if ($entry -like ([string]::Format('{0}*', $RepositoryName))) { - $ModuleContent = Join-Path -Path $ModuleContent -ChildPath $entry; - break; - } - } - - Write-Host ([string]::Format('Using content of folder "{0}" for updates', $ModuleContent)); - - $PluginDirectory = (Join-Path -Path $Archive.ModuleRoot -ChildPath $RepositoryName); - - if ((Test-Path $PluginDirectory) -eq $FALSE) { - Write-Host ([string]::Format('Plugin Module Directory "{0}" is not present. Creating Directory', $PluginDirectory)); - New-Item -Path $PluginDirectory -ItemType Directory | Out-Null; - } - - Write-Host 'Copying files to plugins'; - Copy-ItemSecure -Path (Join-Path -Path $ModuleContent -ChildPath '/*') -Destination $PluginDirectory -Recurse -Force | Out-Null; - - Write-Host 'Cleaning temporary content'; - Start-Sleep -Seconds 1; - Remove-ItemSecure -Path $Archive.Directory -Recurse -Force | Out-Null; - - Unblock-IcingaPowerShellFiles -Path $PluginDirectory; - # In case the plugins are not installed before, load the framework again to - # include the plugins - Use-Icinga; - - Write-Host 'Icinga Plugin update has been completed'; + [Hashtable]$Result = Install-IcingaFrameworkComponent ` + -Name 'plugins' ` + -GitHubUser 'Icinga' ` + -Url $PluginsUrl; return @{ - 'PluginUrl' = $Archive.DownloadUrl + 'PluginUrl' = $Result.RepoUrl; }; } From 699137a97cc7247c43b4e805d158897a5d146cea Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:36:25 +0100 Subject: [PATCH 34/46] Adds support to securely fetch elements from hashtables with default values --- lib/core/tools/Get-IcingaHashtableItem.psm1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/core/tools/Get-IcingaHashtableItem.psm1 diff --git a/lib/core/tools/Get-IcingaHashtableItem.psm1 b/lib/core/tools/Get-IcingaHashtableItem.psm1 new file mode 100644 index 0000000..96f0eef --- /dev/null +++ b/lib/core/tools/Get-IcingaHashtableItem.psm1 @@ -0,0 +1,18 @@ +function Get-IcingaHashtableItem() +{ + param( + $Hashtable, + $Key, + $NullValue = $null + ); + + if ($null -eq $Hashtable) { + return $NullValue; + } + + if ($Hashtable.ContainsKey($Key) -eq $FALSE) { + return $NullValue; + } + + return $Hashtable[$Key]; +} From e8f2152010e5f8900e9dd950b101f17240d08097 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:37:44 +0100 Subject: [PATCH 35/46] Adds unified debug message output with object parsing ability --- lib/core/logging/Icinga_EventLog_Enums.psm1 | 7 ++++++- lib/core/logging/Write-IcingaDebugMessage.psm1 | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/core/logging/Icinga_EventLog_Enums.psm1 b/lib/core/logging/Icinga_EventLog_Enums.psm1 index 30cd05b..2b9c9e5 100644 --- a/lib/core/logging/Icinga_EventLog_Enums.psm1 +++ b/lib/core/logging/Icinga_EventLog_Enums.psm1 @@ -7,7 +7,12 @@ #> [hashtable]$IcingaEventLogEnums += @{ 'Framework' = @{ - # TODO: Add event log messages + 1000 = @{ + 'EntryType' = 'Information'; + 'Message' = 'Generic debug message issued by the Framework or its components'; + 'Details' = 'The Framework or is components can issue generic debug message in case the debug log is enabled. Please ensure to disable it, if not used. You can do so with the command "Disable-IcingaFrameworkDebugMode"'; + 'EventId' = 1000; + }; } }; diff --git a/lib/core/logging/Write-IcingaDebugMessage.psm1 b/lib/core/logging/Write-IcingaDebugMessage.psm1 index 187765c..419e290 100644 --- a/lib/core/logging/Write-IcingaDebugMessage.psm1 +++ b/lib/core/logging/Write-IcingaDebugMessage.psm1 @@ -1,7 +1,8 @@ function Write-IcingaDebugMessage() { param( - [string]$Message + [string]$Message, + [array]$Objects = @() ); if ([string]::IsNullOrEmpty($Message)) { @@ -12,5 +13,8 @@ function Write-IcingaDebugMessage() return; } - Write-EventLog -LogName Application -Source 'Icinga for Windows' -EntryType Information -EventId 1000 -Message $Message; + [array]$DebugContent = @($Message); + $DebugContent += $Objects; + + Write-IcingaEventMessage -EventId 1000 -Namespace 'Framework' -Objects $DebugContent; } From dff16d8f0dab452f3fd921c69ad9d6f36b1fd8f0 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:39:47 +0100 Subject: [PATCH 36/46] Adds support to store debugmode state in config and make it globally available --- icinga-powershell-framework.psm1 | 24 ++++++++++++------- .../Disable-IcingaFrameworkDebugMode.psm1 | 1 + .../Enable-IcingaFrameworkDebugMode.psm1 | 1 + .../Get-IcingaFrameworkDebugMode.psm1 | 10 ++++++++ 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index e0e52e7..1e15d88 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -29,15 +29,6 @@ function Use-Icinga() Import-IcingaLib '\' -Init -Custom; Import-IcingaLib '\' -Init; - $EventLogMessages = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaEventLogMessages*'; - foreach ($entry in $EventLogMessages.Values) { - foreach ($event in $entry.Keys) { - Add-IcingaHashtableItem -Hashtable $global:IcingaEventLogEnums ` - -Key $event ` - -Value $entry[$event] | Out-Null; - } - } - if ($LibOnly -eq $FALSE) { Register-IcingaEventLog; @@ -66,6 +57,21 @@ function Use-Icinga() $global:IcingaDaemonData.FrameworkRunningAsDaemon = $Daemon; } } + + # Enable DebugMode in case it is enabled in our config + if (Get-IcingaFrameworkDebugMode) { + Enable-IcingaFrameworkDebugMode; + $DebugMode = $TRUE; + } + + $EventLogMessages = Invoke-IcingaNamespaceCmdlets -Command 'Register-IcingaEventLogMessages*'; + foreach ($entry in $EventLogMessages.Values) { + foreach ($event in $entry.Keys) { + Add-IcingaHashtableItem -Hashtable $global:IcingaEventLogEnums ` + -Key $event ` + -Value $entry[$event] | Out-Null; + } + } } function Import-IcingaLib() diff --git a/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 index ebd30f5..f32633e 100644 --- a/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 +++ b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 @@ -1,4 +1,5 @@ function Disable-IcingaFrameworkDebugMode() { $global:IcingaDaemonData.DebugMode = $FALSE; + Set-IcingaPowerShellConfig -Path 'Framework.DebugMode' -Value $FALSE; } diff --git a/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 index 9ee7d15..4b3af05 100644 --- a/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 +++ b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 @@ -1,4 +1,5 @@ function Enable-IcingaFrameworkDebugMode() { $global:IcingaDaemonData.DebugMode = $TRUE; + Set-IcingaPowerShellConfig -Path 'Framework.DebugMode' -Value $TRUE; } diff --git a/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 new file mode 100644 index 0000000..fcc5b88 --- /dev/null +++ b/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 @@ -0,0 +1,10 @@ +function Get-IcingaFrameworkDebugMode() +{ + $DebugMode = Get-IcingaPowerShellConfig -Path 'Framework.DebugMode'; + + if ($null -eq $DebugMode) { + return $FALSE; + } + + return $DebugMode; +} From c5e4c4eade328929d04a0970139098c641379921 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:41:30 +0100 Subject: [PATCH 37/46] Improves TCP Stream reader to read messages with correct size --- lib/web/Read-IcingaTCPStream.psm1 | 34 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/web/Read-IcingaTCPStream.psm1 b/lib/web/Read-IcingaTCPStream.psm1 index 3ff3d6e..dddf245 100644 --- a/lib/web/Read-IcingaTCPStream.psm1 +++ b/lib/web/Read-IcingaTCPStream.psm1 @@ -2,27 +2,29 @@ function Read-IcingaTCPStream() { param( [System.Net.Sockets.TcpClient]$Client = @{}, - [System.Net.Security.SslStream]$Stream = $null + [System.Net.Security.SslStream]$Stream = $null, + [int]$ReadLength = 0 ); - # Get the maxium size of our buffer - [byte[]]$bytes = New-Object byte[] $Client.ReceiveBufferSize; - # Read the content of our SSL stream - $Stream.Read($bytes, 0, $Client.ReceiveBufferSize); - - # Now ready the actual content size of the received message - [int]$count = 0; - for ($i=0; $i -le $Client.ReceiveBufferSize; $i++) { - if ($bytes[$i] -ne 0) { - $count = $count + 1; - } else { - break; - } + if ($ReadLength -eq 0) { + $ReadLength = $Client.ReceiveBufferSize; } + if ($null -eq $Stream) { + return $null; + } + + # Get the maxium size of our buffer + [byte[]]$bytes = New-Object byte[] $ReadLength; + + # Read the content of our SSL stream + $MessgeSize = $Stream.Read($bytes, 0, $ReadLength); + + Write-IcingaDebugMessage -Message 'Network Stream message size and content in bytes' -Objects $MessgeSize, $bytes; + # Resize our array to the correct size - [byte[]]$resized = New-Object byte[] $count; - [array]::Copy($bytes, 0, $resized, 0, $count); + [byte[]]$resized = New-Object byte[] $MessgeSize; + [array]::Copy($bytes, 0, $resized, 0, $MessgeSize); # Return our message content return [System.Text.Encoding]::UTF8.GetString($resized); From 199368360239f6b667c4176790533ed4d39e97f3 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:42:46 +0100 Subject: [PATCH 38/46] Fixes REST message reader for reading new msg properly and body read fixing --- lib/web/Read-IcingaRESTMessage.psm1 | 35 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/web/Read-IcingaRESTMessage.psm1 b/lib/web/Read-IcingaRESTMessage.psm1 index 3a0f0c6..9ab382b 100644 --- a/lib/web/Read-IcingaRESTMessage.psm1 +++ b/lib/web/Read-IcingaRESTMessage.psm1 @@ -1,20 +1,34 @@ function Read-IcingaRESTMessage() { param( - [string]$RestMessage = $null + [string]$RestMessage = $null, + [hashtable]$Connection = $null + ); + + # Just in case we didnt receive anything - no need to + # parse through everything + if ([string]::IsNullOrEmpty($RestMessage)) { + return $null; + } + + Write-IcingaDebugMessage ( + [string]::Format( + 'Receiving client message{0}{0}{1}', + (New-IcingaNewline), + $RestMessage + ) ); [hashtable]$Request = @{}; - $RestMessage -match '(\d+) (.+) (.+) (.+)' | Out-Null; + $RestMessage -match '(.+) (.+) (.+)' | Out-Null; - $Request.Add('MessageLength', $Matches[1]); - $Request.Add('Method', $Matches[2]); - $Request.Add('FullRequest', $Matches[3]); + $Request.Add('Method', $Matches[1]); + $Request.Add('FullRequest', $Matches[2]); $Request.Add('RequestPath', @{}); $Request.Add('RequestArguments', @{}); #Path - $PathMatch = $Matches[3]; + $PathMatch = $Matches[2]; $PathMatch -match '((\/[^\/\?]+)*)\??([^\/]*)' | Out-Null; $Arguments = $Matches[3]; $Request.RequestPath.Add('FullPath', $Matches[1]); @@ -53,5 +67,14 @@ function Read-IcingaRESTMessage() $RestMessage -match '(\{(.*\n)*}|\{.*\})' | Out-Null; $Request.Add('Body', $Matches[1]); + # We received a content length, but couldnt load the body. Some clients will send the body as separate message + # Lets try to read the body content + if ($null -ne $Connection) { + if ($Request.ContainsKey('ContentLength') -And $Request.ContentLength -gt 0 -And ($Request.ContainsKey('Body') -eq $FALSE -Or [string]::IsNullOrEmpty($Request.Body))) { + $Request.Body = Read-IcingaTCPStream -Client $Connection.Client -Stream $Connection.Stream -ReadLength $Request.ContentLength; + Write-IcingaDebugMessage -Message 'Body Content' -Objects $Request; + } + } + return $Request; } From 470e5cc036a99b7403ee4c7ce01ea1f3470b32b8 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 16:43:18 +0100 Subject: [PATCH 39/46] Adds error handling for SSLStreams and adds eventlog pre-defined messages --- lib/core/logging/Icinga_EventLog_Enums.psm1 | 12 ++++++++++++ lib/web/New-IcingaSSLStream.psm1 | 9 +++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/core/logging/Icinga_EventLog_Enums.psm1 b/lib/core/logging/Icinga_EventLog_Enums.psm1 index 2b9c9e5..2eae150 100644 --- a/lib/core/logging/Icinga_EventLog_Enums.psm1 +++ b/lib/core/logging/Icinga_EventLog_Enums.psm1 @@ -13,6 +13,18 @@ 'Details' = 'The Framework or is components can issue generic debug message in case the debug log is enabled. Please ensure to disable it, if not used. You can do so with the command "Disable-IcingaFrameworkDebugMode"'; 'EventId' = 1000; }; + 1500 = @{ + 'EntryType' = 'Error'; + 'Message' = 'Failed to securely establish a communiation between this server and the client'; + 'Details' = 'The client connection could not be established between this server. This issue is mostly caused by using Self-Signed/Icinga 2 Agent certificates for the server and the client not trusting the certificate. To resolve this issue, either use trusted certificates signed by your trusted CA or setup the client to accept untrusted certificates'; + 'EventId' = 1500; + }; + 1501 = @{ + 'EntryType' = 'Error'; + 'Message' = 'Client connection was interrupted because of invalid SSL stream'; + 'Details' = 'A client connection was terminated by the Framework because no secure SSL handshake could be established. This issue in general is followed by EventId 1500.'; + 'EventId' = 1501; + }; } }; diff --git a/lib/web/New-IcingaSSLStream.psm1 b/lib/web/New-IcingaSSLStream.psm1 index 556ac12..250566a 100644 --- a/lib/web/New-IcingaSSLStream.psm1 +++ b/lib/web/New-IcingaSSLStream.psm1 @@ -9,8 +9,13 @@ function New-IcingaSSLStream() return $null; } - $SSLStream = New-Object System.Net.Security.SslStream($Client.GetStream(), $false) - $SSLStream.AuthenticateAsServer($Certificate, $false, [System.Security.Authentication.SslProtocols]::Tls12, $true) | Out-Null; + try { + $SSLStream = New-Object System.Net.Security.SslStream($Client.GetStream(), $false) + $SSLStream.AuthenticateAsServer($Certificate, $false, [System.Security.Authentication.SslProtocols]::Tls12, $true) | Out-Null; + } catch { + Write-IcingaEventMessage -EventId 1500 -Namespace 'Framework' -Objects $Client.Client; + return $null; + } return $SSLStream; } From 0081641bd6dbb9c2d2ce39b4bcd7ad06693152a0 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 17:16:17 +0100 Subject: [PATCH 40/46] Fixes spelling in eventlog message --- lib/core/logging/Icinga_EventLog_Enums.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/logging/Icinga_EventLog_Enums.psm1 b/lib/core/logging/Icinga_EventLog_Enums.psm1 index 2eae150..ccb10e0 100644 --- a/lib/core/logging/Icinga_EventLog_Enums.psm1 +++ b/lib/core/logging/Icinga_EventLog_Enums.psm1 @@ -16,7 +16,7 @@ 1500 = @{ 'EntryType' = 'Error'; 'Message' = 'Failed to securely establish a communiation between this server and the client'; - 'Details' = 'The client connection could not be established between this server. This issue is mostly caused by using Self-Signed/Icinga 2 Agent certificates for the server and the client not trusting the certificate. To resolve this issue, either use trusted certificates signed by your trusted CA or setup the client to accept untrusted certificates'; + 'Details' = 'A client connection could not be established to this server. This issue is mostly caused by using Self-Signed/Icinga 2 Agent certificates for the server and the client not trusting the certificate. To resolve this issue, either use trusted certificates signed by your trusted CA or setup the client to accept untrusted certificates'; 'EventId' = 1500; }; 1501 = @{ From 9a2f3cd4291b8f220b05084f26bfd31df8425669 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 27 Mar 2020 17:53:29 +0100 Subject: [PATCH 41/46] Adds support for API arguments without value --- lib/web/Read-IcingaRESTMessage.psm1 | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/web/Read-IcingaRESTMessage.psm1 b/lib/web/Read-IcingaRESTMessage.psm1 index 9ab382b..8e36673 100644 --- a/lib/web/Read-IcingaRESTMessage.psm1 +++ b/lib/web/Read-IcingaRESTMessage.psm1 @@ -40,13 +40,17 @@ function Read-IcingaRESTMessage() $ArgumentsSplit = $Arguments.Split('&'); $ArgumentsSplit+='\\\\\\\\\\\\=FIN'; foreach ( $Argument in $ArgumentsSplit | Sort-Object -descending) { - $Argument -match '(.+)=(.+)' | Out-Null; - If (($Matches[1] -ne $Current) -And ($NULL -ne $Current)) { - $Request.RequestArguments.Add( $Current, $ArgumentContent ); - [array]$ArgumentContent = $null; + if ($Argument.Contains('=')) { + $Argument -match '(.+)=(.+)' | Out-Null; + If (($Matches[1] -ne $Current) -And ($NULL -ne $Current)) { + $Request.RequestArguments.Add( $Current, $ArgumentContent ); + [array]$ArgumentContent = $null; + } + $Current = $Matches[1]; + [array]$ArgumentContent += ($Matches[2]); + } else { + $Request.RequestArguments.Add( $Argument, $null ); } - $Current = $Matches[1]; - [array]$ArgumentContent += ($Matches[2]); } # Header From 21e1d127c555833456e5dba3ddd01e398320299e Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Sat, 28 Mar 2020 18:58:14 +0100 Subject: [PATCH 42/46] Adds support to generate eventlog documentation for event ids --- doc/20-Eventlog.md | 21 ++++++++++++++++++ icinga-powershell-framework.psd1 | 2 +- icinga-powershell-framework.psm1 | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 doc/20-Eventlog.md diff --git a/doc/20-Eventlog.md b/doc/20-Eventlog.md new file mode 100644 index 0000000..25ded22 --- /dev/null +++ b/doc/20-Eventlog.md @@ -0,0 +1,21 @@ +# Framework Eventlog Documentation + +Below you will find a list of EventId's which are exported by this module. The short and detailed message are both written directly into the eventlog. This documentation shall simply provide a summary of available EventId's + +## Event Id 1000 + +| Category | Short Message | Detailed Message | +| --- | --- | --- | +| Information | Generic debug message issued by the Framework or its components | The Framework or is components can issue generic debug message in case the debug log is enabled. Please ensure to disable it, if not used. You can do so with the command "Disable-IcingaFrameworkDebugMode" | + +## Event Id 1500 + +| Category | Short Message | Detailed Message | +| --- | --- | --- | +| Error | Failed to securely establish a communiation between this server and the client | A client connection could not be established to this server. This issue is mostly caused by using Self-Signed/Icinga 2 Agent certificates for the server and the client not trusting the certificate. To resolve this issue, either use trusted certificates signed by your trusted CA or setup the client to accept untrusted certificates | + +## Event Id 1501 + +| Category | Short Message | Detailed Message | +| --- | --- | --- | +| Error | Client connection was interrupted because of invalid SSL stream | A client connection was terminated by the Framework because no secure SSL handshake could be established. This issue in general is followed by EventId 1500. | diff --git a/icinga-powershell-framework.psd1 b/icinga-powershell-framework.psd1 index 61e83b7..6f77fe1 100644 --- a/icinga-powershell-framework.psd1 +++ b/icinga-powershell-framework.psd1 @@ -7,7 +7,7 @@ Copyright = '(c) 2020 Icinga GmbH | MIT' Description = 'Icinga for Windows module which allows to entirely monitor the Windows Host system.' PowerShellVersion = '4.0' - FunctionsToExport = @( 'Use-Icinga', 'Import-IcingaLib', 'Publish-IcingaModuleManifests', 'Get-IcingaPluginDir', 'Get-IcingaCustomPluginDir', 'Get-IcingaCacheDir', 'Get-IcingaPowerShellConfigDir', 'Get-IcingaFrameworkRootPath', 'Get-IcingaPowerShellModuleFile' ) + FunctionsToExport = @( 'Use-Icinga', 'Import-IcingaLib', 'Publish-IcingaModuleManifests', 'Publish-IcingaEventlogDocumentation', 'Get-IcingaPluginDir', 'Get-IcingaCustomPluginDir', 'Get-IcingaCacheDir', 'Get-IcingaPowerShellConfigDir', 'Get-IcingaFrameworkRootPath', 'Get-IcingaPowerShellModuleFile' ) CmdletsToExport = @() VariablesToExport = '*' AliasesToExport = @() diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index 1e15d88..27b0b7d 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -190,6 +190,44 @@ function Publish-IcingaModuleManifests() Set-Content -Path $PSDFile -Value $NewContent; } +function Publish-IcingaEventlogDocumentation() +{ + param( + [string]$Namespace, + [string]$OutFile + ); + + [string]$DocContent = [string]::Format( + '# {0} Eventlog Documentation', + $Namespace + ); + $DocContent += New-IcingaNewLine; + $DocContent += New-IcingaNewLine; + $DocContent += "Below you will find a list of EventId's which are exported by this module. The short and detailed message are both written directly into the eventlog. This documentation shall simply provide a summary of available EventId's"; + + $SortedArray = $IcingaEventLogEnums[$Namespace].Keys.GetEnumerator() | Sort-Object; + + foreach ($entry in $SortedArray) { + $entry = $IcingaEventLogEnums[$Namespace][$entry]; + + $DocContent = [string]::Format( + '{0}{2}{2}## Event Id {1}{2}{2}| Category | Short Message | Detailed Message |{2}| --- | --- | --- |{2}| {3} | {4} | {5} |', + $DocContent, + $entry.EventId, + (New-IcingaNewLine), + $entry.EntryType, + $entry.Message, + $entry.Details + ); + } + + if ([string]::IsNullOrEmpty($OutFile)) { + Write-Host $DocContent; + } else { + Set-Content -Path $OutFile -Value $DocContent; + } +} + function Get-IcingaPluginDir() { return (Join-Path -Path $PSScriptRoot -ChildPath 'lib\plugins\'); From 2fca1d3e2fbb15ae9d1b5c8a91af998564a0ff3a Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Mon, 30 Mar 2020 09:32:54 +0200 Subject: [PATCH 43/46] Adds function to deserialize PSObject properties into name/value keypair --- .../tools/Get-IcingaPSObjectProperties.psm1 | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lib/core/tools/Get-IcingaPSObjectProperties.psm1 diff --git a/lib/core/tools/Get-IcingaPSObjectProperties.psm1 b/lib/core/tools/Get-IcingaPSObjectProperties.psm1 new file mode 100644 index 0000000..f811b91 --- /dev/null +++ b/lib/core/tools/Get-IcingaPSObjectProperties.psm1 @@ -0,0 +1,48 @@ +function Get-IcingaPSObjectProperties() +{ + param( + $Object = $null, + [array]$Include = @(), + [array]$Exclude = @() + ); + + [hashtable]$RetValue = @{}; + + if ($null -eq $Object) { + return $RetValue; + } + + foreach ($property in $Object.PSObject.Properties) { + [string]$DataType = $property.TypeNameOfValue; + + if ($Include.Count -ne 0 -And -Not ($Include -Contains $property.Name)) { + continue; + } + + if ($Exclude.Count -ne 0 -And $Exclude -Contains $property.Name) { + continue; + } + + if ($DataType.Contains('string') -or $DataType.Contains('int') -Or $DataType.Contains('bool')) { + $RetValue.Add( + $property.Name, + $property.Value + ); + } else { + try { + $RetValue.Add( + $property.Name, + (Get-IcingaPSObjectProperties -Object $property.Value) + ); + } catch { + $RetValue.Add( + $property.Name, + ([string]$property.Value) + ); + } + + } + } + + return $RetValue; +} From 429db105a8472af968a34b568c8191eddadc0b3d Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Tue, 28 Apr 2020 13:48:15 +0200 Subject: [PATCH 44/46] Handle deprecated argument --key for Icinga Agent save-cert cmd Fixes #62 --- .../Install-IcingaAgentCertificates.psm1 | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 b/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 index eeb16c5..71699de 100644 --- a/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 +++ b/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 @@ -48,11 +48,19 @@ function Install-IcingaAgentCertificates() Write-Host ([string]::Format('Fetching trusted master certificate from "{0}"', $Endpoint)); - $arguments = [string]::Format('pki save-cert --key {0}{1}.key --trustedcert {0}trusted-parent.crt --host {2}', - $CertificateDirectory, - $Hostname, - $Endpoint - ); + # Argument --key for save-cert is deprecated starting with Icinga 2.12.0 + if (Compare-IcingaVersions -RequiredVersion '2.12.0') { + $arguments = [string]::Format('pki save-cert --trustedcert {0}trusted-parent.crt --host {1}', + $CertificateDirectory, + $Endpoint + ); + } else { + $arguments = [string]::Format('pki save-cert --key {0}{1}.key --trustedcert {0}trusted-parent.crt --host {2}', + $CertificateDirectory, + $Hostname, + $Endpoint + ); + } if ((Start-IcingaAgentCertificateProcess -Arguments $arguments) -eq $FALSE) { Write-Host 'Unable to connect to your provided Icinga CA. Please verify the entered configuration is correct.' ` From 9e7ab3160805fb2ecbc5314caf2972ace0e98b6a Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Tue, 28 Apr 2020 13:52:32 +0200 Subject: [PATCH 45/46] Adds documentation for several Cmdlets --- .../Disable-IcingaFrameworkDebugMode.psm1 | 13 +++++++ .../Enable-IcingaFrameworkDebugMode.psm1 | 15 ++++++++ .../Get-IcingaCheckSchedulerPerfData.psm1 | 17 +++++++++ .../Get-IcingaCheckSchedulerPluginOutput.psm1 | 17 +++++++++ .../Get-IcingaFrameworkDebugMode.psm1 | 15 ++++++++ .../Install-IcingaFrameworkComponent.psm1 | 38 +++++++++++++++++++ 6 files changed, 115 insertions(+) diff --git a/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 index f32633e..fa8f153 100644 --- a/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 +++ b/lib/core/framework/Disable-IcingaFrameworkDebugMode.psm1 @@ -1,3 +1,16 @@ +<# +.SYNOPSIS + Disables the debug mode of the Framework +.DESCRIPTION + Disables the debug mode of the Framework +.FUNCTIONALITY + Disables the Icinga for Windows Debug-Log +.EXAMPLE + PS>Disable-IcingaFrameworkDebugMode; +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + function Disable-IcingaFrameworkDebugMode() { $global:IcingaDaemonData.DebugMode = $FALSE; diff --git a/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 index 4b3af05..2e65fc6 100644 --- a/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 +++ b/lib/core/framework/Enable-IcingaFrameworkDebugMode.psm1 @@ -1,3 +1,18 @@ +<# +.SYNOPSIS + Enables the debug mode of the Framework to print additional details into + the Windows Event Log with Id 1000 +.DESCRIPTION + Enables the debug mode of the Framework to print additional details into + the Windows Event Log with Id 1000 +.FUNCTIONALITY + Enables the Icinga for Windows Debug-Log +.EXAMPLE + PS>Enable-IcingaFrameworkDebugMode; +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + function Enable-IcingaFrameworkDebugMode() { $global:IcingaDaemonData.DebugMode = $TRUE; diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 index 8b750fa..a5a4eda 100644 --- a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 +++ b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 @@ -1,3 +1,20 @@ +<# +.SYNOPSIS + Function to fetch the last executed plugin peformance data + from an internal memory cache in case the Framework is running as daemon. +.DESCRIPTION + While running the Framework as daemon, checkresults for plugins are not + printed into the console but written into an internal memory cache. Once + a plugin was executed, use this function to fetch the plugin performance data +.FUNCTIONALITY + Returns the last performance data output for executed plugins while the + Framework is running as daemon +.OUTPUTS + System.Object +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + function Get-IcingaCheckSchedulerPerfData() { if ($null -eq $IcingaDaemonData) { diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 index 6f2ad89..d6fc83e 100644 --- a/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 +++ b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 @@ -1,3 +1,20 @@ +<# +.SYNOPSIS + Function to fetch the last executed plugin output from an internal memory + cache in case the Framework is running as daemon. +.DESCRIPTION + While running the Framework as daemon, checkresults for plugins are not + printed into the console but written into an internal memory cache. Once + a plugin was executed, use this function to fetch the plugin output +.FUNCTIONALITY + Returns the last checkresult output for executed plugins while the + Framework is running as daemon +.OUTPUTS + System.Object +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + function Get-IcingaCheckSchedulerPluginOutput() { if ($null -eq $IcingaDaemonData) { diff --git a/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 b/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 index fcc5b88..69555e8 100644 --- a/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 +++ b/lib/core/framework/Get-IcingaFrameworkDebugMode.psm1 @@ -1,3 +1,18 @@ +<# +.SYNOPSIS + Get the current debug mode configuration of the Framework +.DESCRIPTION + Get the current debug mode configuration of the Framework +.FUNCTIONALITY + Get the current debug mode configuration of the Framework +.EXAMPLE + PS>Get-IcingaFrameworkDebugMode; +.LINK + https://github.com/Icinga/icinga-powershell-framework +.OUTPUTS + System.Boolean +#> + function Get-IcingaFrameworkDebugMode() { $DebugMode = Get-IcingaPowerShellConfig -Path 'Framework.DebugMode'; diff --git a/lib/core/framework/Install-IcingaFrameworkComponent.psm1 b/lib/core/framework/Install-IcingaFrameworkComponent.psm1 index 2a5b0c6..77121e6 100644 --- a/lib/core/framework/Install-IcingaFrameworkComponent.psm1 +++ b/lib/core/framework/Install-IcingaFrameworkComponent.psm1 @@ -1,3 +1,41 @@ +<# +.SYNOPSIS + Installs a PowerShell Module within the 'icinga-powershell-' namespace + from GitHub or custom locations and installs it into the module directory + the Framework itself is installed to +.DESCRIPTION + Installs a PowerShell Module within the 'icinga-powershell-' namespace + from GitHub or custom locations and installs it into the module directory + the Framework itself is installed to +.FUNCTIONALITY + Download and install a PowerShell module from the 'icinga-powershell-' namespace +.EXAMPLE + PS>Install-IcingaFrameworkComponent -Name 'plugins' -Stable; +.EXAMPLE + PS>Install-IcingaFrameworkComponent -Name 'plugins' -Stable -DryRun; +.PARAMETER Name + The name of the module to install. The namespace 'icinga-powershell-' is added + by the function automatically +.PARAMETER GitHubUser + Overwrite the default GitHub user for a different one to download modules from +.PARAMETER Url + Specify a direct Url to a ZIP-Archive for external or local web ressources or + local network shares +.PARAMETER Stable + Download the latest stable version from a GitHub source +.PARAMETER Snapshot + Download the latest master branch from a GitHub source +.PARAMETER DryRun + Only fetch possible Urls and return the result. No download or installation + will be done +.INPUTS + System.String +.OUTPUTS + System.Hashtable +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + function Install-IcingaFrameworkComponent() { param( From 5cb885dc1485a3e3faf51de16d1659e112ecc4cc Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Thu, 30 Apr 2020 13:48:45 +0200 Subject: [PATCH 46/46] Adds tool function for easily creating basic auth headers --- lib/core/tools/New-IcingaBasicAuthHeader.psm1 | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/core/tools/New-IcingaBasicAuthHeader.psm1 diff --git a/lib/core/tools/New-IcingaBasicAuthHeader.psm1 b/lib/core/tools/New-IcingaBasicAuthHeader.psm1 new file mode 100644 index 0000000..0728747 --- /dev/null +++ b/lib/core/tools/New-IcingaBasicAuthHeader.psm1 @@ -0,0 +1,53 @@ +<# +.SYNOPSIS + Creates a basic auth header for web requests in case the Get-Credential + method is not supported or working properly +.DESCRIPTION + Creates a basic auth header for web requests in case the Get-Credential + method is not supported or working properly +.FUNCTIONALITY + Creates a hashtable with a basic authorization header as Base64 encoded +.EXAMPLE + PS>New-IcingaBasicAuthHeader -Username 'example_user' -Password $SecurePasswordString; +.EXAMPLE + PS>New-IcingaBasicAuthHeader -Username 'example_user' -Password (Read-Host -Prompt 'Please enter your password' -AsSecureString); +.EXAMPLE + PS>New-IcingaBasicAuthHeader -Username 'example_user' -Password (ConvertTo-IcingaSecureString 'my_secret_password'); +.PARAMETER Username + The user we will use to authenticate for +.PARAMETER Password + The password for the user provided as SecureString +.INPUTS + System.String +.OUTPUTS + System.Hashtable +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + +function New-IcingaBasicAuthHeader() +{ + param( + [string]$Username = $null, + [SecureString]$Password = $null + ); + + if ($null -eq $Password -or [string]::IsNullOrEmpty($Username)) { + Write-Host 'Please specify your Username and Password to continue'; + return @{}; + } + + $Credentials = [System.Convert]::ToBase64String( + [System.Text.Encoding]::ASCII.GetBytes( + [string]::Format( + '{0}:{1}', + $Username, + (ConvertFrom-IcingaSecureString $Password) + ) + ) + ); + + return @{ + 'Authorization' = [string]::Format('Basic {0}', $Credentials) + }; +}