From d8f1ac024efe88573f3b202119716e30d9f0027b Mon Sep 17 00:00:00 2001 From: Nathan Windisch Date: Thu, 25 Jul 2024 02:31:34 +0100 Subject: [PATCH] Initial commit --- Functions/Connect-GraylogService.ps1 | 66 ++++++++ Functions/ConvertFrom-GraylogSession.ps1 | 56 +++++++ Functions/ConvertTo-GraylogSession.ps1 | 52 +++++++ Functions/Disconnect-GraylogService.ps1 | 26 ++++ Functions/Get-GraylogStreamId.ps1 | 31 ++++ Functions/Initialize-GraylogServiceVault.ps1 | 41 +++++ Functions/Invoke-GraylogRequest.ps1 | 61 ++++++++ Functions/Receive-GraylogSearchJob.ps1 | 58 +++++++ Functions/Search-Graylog.ps1 | 151 +++++++++++++++++++ Functions/Test-GraylogSession.ps1 | 45 ++++++ PSGraylog.psd1 | 149 ++++++++++++++++++ PSGraylog.psm1 | 2 + README.md | 28 ++++ 13 files changed, 766 insertions(+) create mode 100644 Functions/Connect-GraylogService.ps1 create mode 100644 Functions/ConvertFrom-GraylogSession.ps1 create mode 100644 Functions/ConvertTo-GraylogSession.ps1 create mode 100644 Functions/Disconnect-GraylogService.ps1 create mode 100644 Functions/Get-GraylogStreamId.ps1 create mode 100644 Functions/Initialize-GraylogServiceVault.ps1 create mode 100644 Functions/Invoke-GraylogRequest.ps1 create mode 100644 Functions/Receive-GraylogSearchJob.ps1 create mode 100644 Functions/Search-Graylog.ps1 create mode 100644 Functions/Test-GraylogSession.ps1 create mode 100644 PSGraylog.psd1 create mode 100644 PSGraylog.psm1 create mode 100644 README.md diff --git a/Functions/Connect-GraylogService.ps1 b/Functions/Connect-GraylogService.ps1 new file mode 100644 index 0000000..0d6395d --- /dev/null +++ b/Functions/Connect-GraylogService.ps1 @@ -0,0 +1,66 @@ +function Connect-GraylogService { + <# + .SYNOPSIS + Connects to the Graylog server. + .DESCRIPTION + Connects to the Graylog server using the provided credentials. + .PARAMETER Credential + The credentials to use to connect to the Graylog server. + .OUTPUTS + None, the session is stored in the global variable $GraylogSession. + .EXAMPLE + Connect-GraylogService + Connects to the Graylog server using the stored credentials. + .EXAMPLE + Connect-GraylogService -Credential (Get-Credential) + Connects to the Graylog server using the provided credentials prompting for both the username and password. + .EXAMPLE + Connect-GraylogService -Credential (Get-Credential -UserName "ab123456") + Connects to the Graylog server using the provided credentials, prompting for just a password. + .EXAMPLE + $Credential = [PSCredential]::new("ab123456", (ConvertTo-SecureString "Password123" -AsPlainText -Force)) + Connect-GraylogService -Credential $Credential + Connects to the Graylog server using the provided credentials without prompting for any input. (Other methods of creating a PSCredential object can be used) + .NOTES + The session is stored in the global variable $GraylogSession and is used for subsequent requests to the Graylog server. + #> + [Alias("Connect-Graylog")] + param ( + [Parameter()] + [PSCredential] + $Credential + ) + if ((Test-Session)) { return } # If the session is still valid, don't create a new one + if (-NOT $Credential) { + try { + $Credential = Get-Secret Graylog_Credential -ErrorAction Stop + } catch { + try { + Write-Host "The Graylog Credential secret is missing, prompting for input..." + $Credential = Get-Credential -Message "Enter your Graylog credentials (the same way as you would via the web service)" + $Credential | Set-Secret Graylog_Credential -ErrorAction Stop + } catch { + throw "Failed to set the Graylog Credential secret: $_" + } + } + } + $Request = @{ + Method = "POST" + URI = "$(Get-Secret Graylog_BaseURI -AsPlainText)/api/system/sessions" + Body = ConvertTo-Json @{ + host = $BaseURI.Authority + username = $Credential.Username.Split("@")[0] + password = $Credential.GetNetworkCredential().Password + } + ContentType = "application/json" + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML; like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0" + Headers = @{ "X-Requested-By" = "XMLHttpRequest" } + SessionVariable = "GraylogSession" + } + # Use a regular Invoke-RestMethod for the initial sign-in request to avoid any issues with the WebSession + try { $null = Invoke-RestMethod @Request } + catch { throw $_ } + + $GraylogSession | ConvertFrom-GraylogSession | Set-Secret Graylog_Session +} +# Export-ModuleMember -Function Connect-Graylog \ No newline at end of file diff --git a/Functions/ConvertFrom-GraylogSession.ps1 b/Functions/ConvertFrom-GraylogSession.ps1 new file mode 100644 index 0000000..4f9fe27 --- /dev/null +++ b/Functions/ConvertFrom-GraylogSession.ps1 @@ -0,0 +1,56 @@ +function ConvertFrom-GraylogSession { + <# + .SYNOPSIS + Converts a WebRequestSession object to a JSON string. + .DESCRIPTION + Converts a WebRequestSession object to a JSON string. + .PARAMETER InputObject + The WebRequestSession object to convert to a JSON string. + .OUTPUTS + A JSON string containing the properties of the WebRequestSession object. + .EXAMPLE + ConvertFrom-Session -WebSession $global:GraylogSession + Converts the WebRequestSession object to a JSON string. + .EXAMPLE + $global:GraylogSession | ConvertFrom-Session + Converts the WebRequestSession object to a JSON string. + #> + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [Microsoft.PowerShell.Commands.WebRequestSession] + $InputObject + ) + + try { $local:Graylog_BaseURI = Get-Secret Graylog_BaseURI -AsPlainText } + catch { Initialize-ServiceVault } + # $Output = @{ + # Headers = $InputObject.Headers + # Cookies = [Collections.Generic.List[PSCustomObject]]::new() + # UseDefaultCredentials = $InputObject.UseDefaultCredentials + # Credentials = $InputObject.Credentials + # Certificates = $InputObject.Certificates + # UserAgent = $InputObject.UserAgent + # Proxy = $InputObject.Proxy + # MaximumRedirection = $InputObject.MaximumRedirection + # MaximumRetryCount = $InputObject.MaximumRetryCount + # RetryIntervalInSeconds = $InputObject.RetryIntervalInSeconds + # } + $Output = $InputObject + if ($InputObject.Cookies.Count) { + # GetAllCookies is only available in PowerShell Core :c + $InputObject.Cookies.GetCookies($local:Graylog_BaseURI) | ForEach-Object { + $Output.Cookies.Add( + [PSCustomObject]@{ + Name = $_.Name + Value = $_.Value + Domain = $_.Domain + Path = $_.Path + Expires = $_.Expires + Secure = $_.Secure + HttpOnly = $_.HttpOnly + }) + } + } + + return ConvertTo-Json $Output +} \ No newline at end of file diff --git a/Functions/ConvertTo-GraylogSession.ps1 b/Functions/ConvertTo-GraylogSession.ps1 new file mode 100644 index 0000000..b131a5a --- /dev/null +++ b/Functions/ConvertTo-GraylogSession.ps1 @@ -0,0 +1,52 @@ + +function ConvertTo-GraylogSession { + <# + .SYNOPSIS + Converts a JSON string to a WebRequestSession object. + .DESCRIPTION + Converts a JSON string to a WebRequestSession object. + .PARAMETER InputObject + The JSON string to convert to a WebRequestSession object. + .OUTPUTS + A WebRequestSession object containing the properties from the JSON string. + .EXAMPLE + ConvertTo-Session -InputObject $Json + Converts the JSON string to a WebRequestSession object. + .EXAMPLE + $Json | ConvertTo-Session + Converts the JSON string to a WebRequestSession object. + .NOTES + This function is used to convert a JSON string to a WebRequestSession object. + #> + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [string] + $InputObject + ) + + $Object = ConvertFrom-Json $InputObject + $Session = [Microsoft.PowerShell.Commands.WebRequestSession]::new() + $Object.Headers.PSObject.Properties | ForEach-Object { $Session.Headers.Add($_.Name, $_.Value) } + $Session.UseDefaultCredentials = $Object.UseDefaultCredentials + $Session.Credentials = $Object.Credentials + $Session.Certificates = $Object.Certificates + $Session.UserAgent = $Object.UserAgent + $Session.Proxy = $Object.Proxy + $Session.MaximumRedirection = $Object.MaximumRedirection + try { $Session.MaximumRetryCount = $Object.MaximumRetryCount } catch {} # MaximumRetryCount is only available in PowerShell Core + try { $Session.RetryIntervalInSeconds = $Object.RetryIntervalInSeconds } catch {} # RetryIntervalInSeconds is only available in PowerShell Core + if ($Object.Cookies) { + $Object.Cookies | ForEach-Object { + $Cookie = [Net.Cookie]::new() + $Cookie.Name = $_.Name + $Cookie.Value = $_.Value + $Cookie.Domain = $_.Domain + $Cookie.Path = $_.Path + $Cookie.Expires = $_.Expires + $Cookie.Secure = $_.Secure + $Cookie.HttpOnly = $_.HttpOnly + $Session.Cookies.Add($Cookie) + } + } + return $Session +} \ No newline at end of file diff --git a/Functions/Disconnect-GraylogService.ps1 b/Functions/Disconnect-GraylogService.ps1 new file mode 100644 index 0000000..72bd1c2 --- /dev/null +++ b/Functions/Disconnect-GraylogService.ps1 @@ -0,0 +1,26 @@ +function Disconnect-GraylogService { + <# + .SYNOPSIS + Disconnects from the Graylog server. + .DESCRIPTION + Disconnects from the Graylog server by deleting the session using the current WebSession data. By default, the stored credentials are not removed. + .PARAMETER Force + An optional switch to force the removal of the stored credentials as well as the session. + .OUTPUTS + None + .EXAMPLE + Disconnect-GraylogService + Disconnects from the Graylog server. + .NOTES + Garbage collection is called after the session is deleted to ensure that the session is removed from memory. + #> + [Alias("Disconnect-Graylog")] + param ( + [Parameter()] + [Switch] + $Force + ) + Invoke-GraylogRequest DELETE "/api/system/sessions" + Remove-Secret Graylog_Session + if ($Force) { Remove-Secret Graylog_Credential } +} diff --git a/Functions/Get-GraylogStreamId.ps1 b/Functions/Get-GraylogStreamId.ps1 new file mode 100644 index 0000000..a8a92c6 --- /dev/null +++ b/Functions/Get-GraylogStreamId.ps1 @@ -0,0 +1,31 @@ +function Get-GraylogStreamId { + <# + .SYNOPSIS + Gets the ID of a log stream in Graylog. + .DESCRIPTION + Gets the ID of a log stream in Graylog using the provided log name. + .PARAMETER LogName + The name of the log stream to get the ID for, the log name has to be an exact match for a known log stream. + .OUTPUTS + The ID of the log stream if it exists, otherwise null. + .EXAMPLE + Get-GraylogStreamId -LogName "Windows Security" + Gets the ID of the log stream with the name "Windows Security" (645b6fdd9d3689589cdce1bc). + #> + param ( + [Parameter(Mandatory)] + [ValidateSet("Infoblox", "Infoblox DNS", "Windows Application", "Windows Security", "Windows System")] + [string] + $LogName + ) + + # TODO: Use Secret Management module to get the Graylog API URI + if ($null -eq $global:GraylogStreams) { + $Response = Invoke-GraylogRequest GET "/streams" + $global:GraylogStreams = $Response.Streams + } + + $Stream = $global:GraylogStreams.Where{$_.Title -eq $LogName} + if ($null -eq $Stream) { return $null } + return $Stream.Id +} \ No newline at end of file diff --git a/Functions/Initialize-GraylogServiceVault.ps1 b/Functions/Initialize-GraylogServiceVault.ps1 new file mode 100644 index 0000000..045ea5a --- /dev/null +++ b/Functions/Initialize-GraylogServiceVault.ps1 @@ -0,0 +1,41 @@ +function Initialize-GraylogServiceVault { + try { + $null = Get-SecretVault "Graylog" -ErrorAction Stop + } catch { + try { + $null = Register-SecretVault "Graylog" -ModuleName "Microsoft.PowerShell.SecretStore" -ErrorAction Stop + } catch { + throw "Failed to create the Graylog secret vault: $_" + } + } + + try { + $null = Get-Secret Graylog_BaseURI -ErrorAction Stop + } catch { + try { + do { + Write-Host "The Graylog Base URI secret is missing, prompting for input..." + $local:Graylog_Host = [string](Read-Host -Prompt "Enter Graylog Host (e.g. graylog.example.com)") + $local:Graylog_Port = [int](Read-Host -Prompt "Enter Graylog Port (e.g. 80, 443, 9000, etc)") + $local:Graylog_IsHTTPS = [bool]$Host.UI.PromptForChoice("Graylog Base URI - Is the application served over HTTPS?", "(Is there a lock symbol when you visit the app?)", @("&No", "&Yes"), 0) + $local:Graylog_Protocol = if ($local:Graylog_IsHTTPS) { "https" } else { "http" } + $local:Graylog_BaseURI = "${local:Graylog_Protocol}://${local:Graylog_Host}:${local:Graylog_Port}" + $local:Graylog_IsBaseURICorrect = [bool]$Host.UI.PromptForChoice("Graylog Base URI - Is the following URI correct?", $local:Graylog_BaseURI, @("&No", "&Yes"), 0) + } while (-NOT $local:Graylog_IsBaseURICorrect) + $null = $local:Graylog_BaseURI | Set-Secret Graylog_BaseURI -ErrorAction Stop + } catch { + throw "Failed to set the Graylog Base URI secret: $_" + } + } + + try { + $null = Get-Secret Graylog_Credential -ErrorAction Stop + } catch { + try { + Write-Host "The Graylog Credential secret is missing, prompting for input..." + Get-Credential -Message "Enter your Graylog credentials (the same way as you would via the web service)" | Set-Secret Graylog_Credential -ErrorAction Stop + } catch { + throw "Failed to set the Graylog Credential secret: $_" + } + } +} \ No newline at end of file diff --git a/Functions/Invoke-GraylogRequest.ps1 b/Functions/Invoke-GraylogRequest.ps1 new file mode 100644 index 0000000..193523e --- /dev/null +++ b/Functions/Invoke-GraylogRequest.ps1 @@ -0,0 +1,61 @@ +function Invoke-GraylogRequest { + <# + .SYNOPSIS + Invokes a request to the Graylog API. + .DESCRIPTION + Invokes a request to the Graylog API using the provided parameters and HTTP method. + .PARAMETER Method + The HTTP method to use for the request (default: GET, available: DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH). + .PARAMETER Path + The path to the API endpoint to invoke the request to. + .PARAMETER Body + The (optional) body of the request to send to the API endpoint, which should be provided as a JSON string (or eqivalent object for other formats). + .PARAMETER ContentType + The content type of the request body (default: application/json). + .OUTPUTS + The response from the Graylog API. + .EXAMPLE + Invoke-GraylogRequest GET "/api/system/sessions" + .EXAMPLE + Invoke-GraylogRestRequest -Method GET -Path "/api/system/sessions" + Invokes a GET request to the /api/system/sessions endpoint in the Graylog API using the global WebSession object. + .EXAMPLE + Invoke-GraylogRestRequest -Method POST -Path "/api/system/sessions" -Body @{ + host = $BaseURI.Authority + username = $Credential.Username.Split("@")[0] + password = $Credential.GetNetworkCredential().Password + } + Invokes a POST request to the /api/system/sessions endpoint in the Graylog API using the global WebSession object and the provided body. + .NOTES + This function is used to invoke requests to the Graylog API using the provided parameters. + The Method, Path, and WebSession parameters are required, while the Body, ContentType, Headers, and UserAgent parameters are optional. + #> + [Alias("igsr")] + param ( + [Parameter(Mandatory)] + [ValidateSet("DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH")] + [string] + $Method, + + [Parameter(Mandatory)] + [string] + $Path, + + [Parameter()] + [string] + $Body, + + [Parameter()] + [string] + $ContentType = "application/json" + ) + $Session = Get-Secret Graylog_Session Graylog -AsPlainText -ErrorAction Stop + $Request = @{ + Method = $Method + URI = "$(Get-Secret Graylog_BaseURI -AsPlainText)/api/$($Path.TrimStart('/api'))" + WebSession = (ConvertTo-GraylogSession $Session) + ContentType = $ContentType + } + if ($Body) { $Request.Body = $Body } + Invoke-RestMethod @Request +} \ No newline at end of file diff --git a/Functions/Receive-GraylogSearchJob.ps1 b/Functions/Receive-GraylogSearchJob.ps1 new file mode 100644 index 0000000..bb30739 --- /dev/null +++ b/Functions/Receive-GraylogSearchJob.ps1 @@ -0,0 +1,58 @@ +function Receive-GraylogSearchJob { + <# + .SYNOPSIS + Retrieves the results of a search job in Graylog. + .DESCRIPTION + Retrieves the results of a search job in Graylog using the provided SearchId, QueryId, and Filter + Alternatively, a PSCustomObject containing the SearchId, QueryId, and FilterId can be provided to retrieve the results. + .PARAMETER Job + A PSCustomObject containing the SearchId, QueryId, and FilterId of the search job. This parameter cannot be used with the other *Id parameters. + .PARAMETER SearchId + The ID of the search job to retrieve the results for. + .PARAMETER QueryId + The ID of the query to retrieve the results for. + .PARAMETER FilterId + The ID of the filter to retrieve the results for. + .OUTPUTS + The results of the search job. + .EXAMPLE + Receive-GraylogSearchJob -Job $Job + Retrieves the results of the search job using the provided PSCustomObject containing the SearchId, QueryId, and FilterId. + .EXAMPLE + Receive-GraylogSearchJob -SearchId "..." -QueryId "..." -FilterId ".." + Retrieves the results of the search job with the provided SearchId, QueryId, and FilterId. + .NOTES + The SearchId, QueryId, and FilterId are used to retrieve the results of the search job using the Receive-GraylogJob function. + These can either be provided as the Job parameter, or as individual parameters. + #> + param ( + [Parameter(Mandatory, ParameterSetName="ByPSCustomObject", ValueFromPipeline, ValueFromRemainingArguments)] + [PSCustomObject] + $Job, + + [Parameter(Mandatory={-NOT $Job}, ParameterSetName="ById")] + [string] + $SearchId, + + [Parameter(Mandatory={-NOT $Job}, ParameterSetName="ById")] + [string] + $QueryId, + + [Parameter(Mandatory={-NOT $Job}, ParameterSetName="ById")] + [string] + $FilterId + ) + if ($PSCmdlet.ParameterSetName -eq "ByPSCustomObject") { + $SearchId = $Job.SearchId + $QueryId = $Job.QueryId + $FilterId = $Job.FilterId + } + $Body = ConvertTo-Json @{ + global_override = @{ keep_queries = @($QueryId) } + parameter_bindings = @{} + } + $Response = Invoke-GraylogRequest POST "/views/search/$SearchId/execute" $Body + $Data = $Response.results.$QueryId.search_types.$FilterId + if ($Data.total_results -eq 0) { throw "No results found for the search job '$SearchId'." } + return $Data.messages.message +} diff --git a/Functions/Search-Graylog.ps1 b/Functions/Search-Graylog.ps1 new file mode 100644 index 0000000..714b027 --- /dev/null +++ b/Functions/Search-Graylog.ps1 @@ -0,0 +1,151 @@ +function Search-Graylog { + <# + .SYNOPSIS + Queries Graylog for log data using the provided query and log stream name. + .DESCRIPTION + Query Graylog for log data using the provided query and log stream name. The query should contain the key:value pairs to search for, and the key should be the CASE-SENSITIVE field name to search within. + .PARAMETER Query + The search query to execute, should contain the key:value pairs to search for. + Example: "EventID:4740 && TargetUserName:ab123456" will search for all account lockouts for the user ab123456. + (Note that the LogId would need to be changed to the unique identifier for the Active Directory log stream in the above example) + .PARAMETER TimeSpan + The time span to search within, defaults to 7 days. + .PARAMETER LogName + The name of the log stream to search within, defaults to "Windows Security". + .PARAMETER Limit + The maximum number of results to return, defaults to 150. + .PARAMETER SortField + The field to sort the results by, defaults to "timestamp". + .PARAMETER SortOrder + The order to sort the results by, defaults to "desc". + .PARAMETER AsJob + An optional switch to run the search job as a background job. + .PARAMETER Detailed + An optional switch to return the search job details instead of the results (only does anything when -AsJob is not used). + .OUTPUTS + A PSCustomObject containing the SearchId, QueryId, and FilterId of the search job. + .EXAMPLE + Start-GraylogJob -Query "EventID:4740 && TargetUserName:ab123456" + Starts a search job for all account lockouts for the user ab123456. + .NOTES + A identifier for various parts of the search job (SearchId, QueryId, FilterId) are generated and returned in a PSCustomObject, which can be used to retrieve the results of the search job. + The SearchId, QueryId, and FilterId are used to retrieve the results of the search job using the Receive-GraylogJob function. + #> + [Alias("Search-Graylog")] + param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $Query, + + [Parameter()] + [TimeSpan] + $TimeSpan = [TimeSpan]::FromDays(7), + + [Parameter()] + [ValidateSet("Infoblox", "Infoblox DNS", "Windows Application", "Windows Security", "Windows System")] + [string] + $LogName = "Windows Security", + + [Parameter()] + [int] + $Limit = 150, + + [Parameter()] + [string] + $SortField = "timestamp", + + [Parameter()] + [ValidateSet("desc", "asc")] + [string] + $SortOrder = "desc", + + [Parameter()] + [switch] + $AsJob, + + [Parameter()] + [switch] + $Detailed + ) + + $LogId = Get-GraylogLogStreamId -LogName $LogName + if ($null -eq $LogId) { throw "The log stream '$LogName' does not exist." } + $SearchId = [String]::Join('', [GUID]::NewGUID().GUID.Replace("-", "")[0..23]) # Generate a unique identifier for the search + $QueryId = [GUID]::NewGUID().GUID.ToString() # Generate a unique identifier for the query + $FilterId = [GUID]::NewGUID().GUID.ToString() # Generate a unique identifier for the filter + + $Request = @{ + Method = "POST" + Path = "/views/search" + Body = ConvertTo-JSON -Depth 7 @{ + id = $SearchId + queries = @( + @{ + id = $QueryId + query = @{ + type = "elasticsearch" + query_string = $Query + } + timerange = @{ + type = "relative" + from = $TimeSpan.TotalSeconds + } + filter = @{ + type = "or" + filters = @(@{ + type = "stream" + id = $LogId + }) + } + filters = @() + search_types = @(@{ + id = $FilterId + query = $null + timerange = $null + offset = 0 + streams = @() + decorators = @() + type = "messages" + limit = $Limit + filters = @() + sort = @(@{ + field = $SortField.ToLower() + order = $SortOrder.ToUpper() + }) + }) + } + ) + parameters = @() + } + } + $QuerySuccess = $true + try { $null = Invoke-GraylogRequest @Request } + catch { $QuerySuccess = $false } + + if ($AsJob) { + return [PSCustomObject]@{ + Success = $QuerySuccess + SearchId = $SearchId + QueryId = $QueryId + FilterId = $FilterId + Request = $Request + } + } else { + $RetrievalSuccess = $true + try { $Data = Receive-GraylogSearchJob -SearchId $SearchId -QueryId $QueryId -FilterId $FilterId } + catch { $RetrievalSuccess = $false } + if (-NOT $Detailed) { return $Data } + else { + return [PSCustomObject]@{ + QuerySuccess = $QuerySuccess + RetrievalSuccess = $RetrievalSuccess + Data = $Data + SearchId = $SearchId + QueryId = $QueryId + FilterId = $FilterId + Request = $Request + } + } + } +} diff --git a/Functions/Test-GraylogSession.ps1 b/Functions/Test-GraylogSession.ps1 new file mode 100644 index 0000000..fd8b2aa --- /dev/null +++ b/Functions/Test-GraylogSession.ps1 @@ -0,0 +1,45 @@ +function Test-GraylogSession { + <# + .SYNOPSIS + Tests the current session to the Graylog server. + .DESCRIPTION + Tests the current session to the Graylog server to ensure that it is still valid. + .PARAMETER SkipSecretCheck + An optional switch to skip the check for the required secrets. + .PARAMETER SkipSessionCheck + An optional switch to skip the check for the session secret. + .OUTPUTS + True if the session is valid, otherwise false. + .EXAMPLE + Test-GraylogSession + Tests the current session to the Graylog server. + .NOTES + This function is used to test the current session to the Graylog server to ensure that it is still valid. + #> + param ( + [Parameter()] + [Switch] + $SkipSecretCheck, + + [Parameter()] + [Switch] + $SkipSessionCheck + ) + + if (-NOT $SkipSecretCheck) { + try { $null = Get-Secret Graylog_BaseURI -ErrorAction Stop } + catch { Write-Error "The Graylog BaseURI secret is missing, try running Initialize-GraylogServiceVault?"; return $false } + try { $null = Get-Secret Graylog_Credential -ErrorAction Stop } + catch { Write-Error "The Graylog Credential secret is missing, try running Initialize-GraylogServiceVault?"; return $false } + try { $null = Get-Secret Graylog_Session -ErrorAction Stop } + catch { Write-Error "The Graylog Session secret is missing, try running Connect-GraylogService?"; return $false } + if ($SkipSessionCheck) { return $true } + } + + if (-NOT $SkipSessionCheck) { + try { return (Invoke-GraylogRequest GET "/system/sessions").is_valid } + catch { return $false } + } + + throw "-SkipSecretCheck and -SkipSessionCheck are mutually exclusive." +} diff --git a/PSGraylog.psd1 b/PSGraylog.psd1 new file mode 100644 index 0000000..bf23a40 --- /dev/null +++ b/PSGraylog.psd1 @@ -0,0 +1,149 @@ +# +# Module manifest for module 'PSGraylog' +# +# Generated by: Nathan Windisch +# +# Generated on: 2024-07-24 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'PSGraylog.psm1' + +# Version number of this module. +ModuleVersion = '0.0.1' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = 'a145661b-ef85-4b71-8fa3-cafbb7e210a5' + +# Author of this module +Author = 'Nathan Windisch ' + +# Company or vendor of this module +CompanyName = 'wnd.sh' + +# Copyright statement for this module +Copyright = '(c) Nathan Windisch . All rights reserved.' + +# Description of the functionality provided by this module +Description = 'A PowerShell interface for Graylog' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +RequiredModules = @("Microsoft.PowerShell.SecretManagement", "Microsoft.PowerShell.SecretStore") + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = @( + "Functions\Connect-GraylogService.ps1", + "Functions\Disconnect-GraylogService.ps1", + "Functions\Get-GraylogStreamId.ps1", + "Functions\Initialize-GraylogServiceVault.ps1", + "Functions\Invoke-GraylogRequest.ps1", + "Functions\Receive-GraylogSearchJob.ps1", + "Functions\Search-Graylog.ps1", + "Functions\Test-GraylogSession.ps1", + "Functions\ConvertFrom-GraylogSession.ps1", + "Functions\ConvertTo-GraylogSession.ps1" +) + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @( + "Connect-GraylogService", + "Disconnect-GraylogService", + "Get-GraylogStreamId", + "Initialize-GraylogServiceVault", + "Invoke-GraylogRequest", + "Receive-GraylogSearchJob", + "Search-Graylog", + "Test-GraylogSession", + "ConvertFrom-GraylogSession", + "ConvertTo-GraylogSession" +) + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @("igsr", "Connect-Graylog", "Disconnect-Graylog") + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + PSData = @{ + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = "" +} \ No newline at end of file diff --git a/PSGraylog.psm1 b/PSGraylog.psm1 new file mode 100644 index 0000000..6ff3d9a --- /dev/null +++ b/PSGraylog.psm1 @@ -0,0 +1,2 @@ +Import-Module "$PSScriptRoot/Functions/Initialize-GraylogServiceVault.ps1" +$null = Initialize-GraylogServiceVault \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..46a750d --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# PSGraylog +*A PowerShell interface for Graylog* + +# Getting Started +1. First, install and import the module from your local PSGallery repo. + You should be prompted to set up your Graylog host, and your credentials. + (You can always re-run this with `Initialize-GraylogServiceVault`) + ```pwsh + TODO: How to install from a repo + Install-Module PSGraylog + Import-Module PSGraylog + ``` +2. Then, connect to Graylog. + ```pwsh + Connect-GraylogService + ``` +3. Finally, run a query (the default for the -LogName parameter is 'Windows Security', which is (in my environment, anyways) Active Directory logs): + ```pwsh + $Query = "EventID:4740 && TargetUsername:ab123456" + Search-Graylog $Query + ``` +4. If you want to re-use the data, you can use the -AsJob parameter to return a GraylogSearchJob object. + This object contains various identifers used to locate the search query, and is much quicker then + re-running the query. + ```pwsh + $Job = Search-Graylog $Query -AsJob + $Job | Receive-GraylogSearchJob + ```