diff --git a/.vscode/settings.json b/.vscode/settings.json index 470eddc..e53147f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { // When enabled, will trim trailing whitespace when you save a file. - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "editor.formatOnSave": false, + "powershell.codeFormatting.preset": "Allman" } diff --git a/README.md b/README.md index ce35ffb..162c966 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# WazuhOSSec +# WazuhOSSecDSC Powershell DSC Class based resource for installing and configuring the Wazuh Agent -The **WazuhOSSec** DSC resources allow you to install and register the Wazuh Ossec Agent with the defined Wazuh Server. +The **WazuhOSSecDSC** resources allow you to install and register the Wazuh Ossec Agent with the defined Wazuh Server. ## Description -The **WazuhOSSec** DSC module contains the **WazuhAgentInstall** and **WazuhAgentRegister** DSC Resources. These resources were built using PowerShell Classes and such will require Powershell 5.0 +. These DSC resources allow you to install the Wazuh agent and regster it with the Wazuh server. +The **WazuhOSSecDSC** module contains the **WazuhAgentInstall** and **WazuhAgentRegister** DSC Resources. These resources were built using PowerShell Classes and as such will require Powershell 5.0+. These DSC resources allow you to install the Wazuh agent and register it with the Wazuh server. ## Resources * **WazuhAgentInstall** Installs or Upgrades the Wazuh Agent from a path you provide. -* **WazuhAgentRegister** Registers an agent with the Wazuh server +* **WazuhAgentRegister** Registers or Deletes an agent on the Wazuh Manager. ### **WazuhAgentInstall** * **InstallerPath** - Path to the Wazuh Agent installer on the local server. -* **Ensure** - Install or uninstall the agent. (Present/Absent) +* **Ensure** - Install or uninstall the agent. (Present/Absent). ### **WazuhAgentRegister** * **AgentName** - The name you want to register for the Agent. @@ -22,7 +22,7 @@ The **WazuhOSSec** DSC module contains the **WazuhAgentInstall** and **WazuhAgen * **UseSelfSignedCerts** - Determines whether to use a self signed cert with the Wazuh server. Default is false. * **ApiPollingInterval** - Used to determine a polling interval for checking if the agent is registered. Default is 0 for no interval meaning the resource will poll the server everytime DSC is run. Specified in minutes. * **Credential** - PSCredential object passed into the rersource for authenticating to the Wazuh server -* **Ensure** - Register the agent with the wazuh server. (Present/Absent) +* **Ensure** - Registers or Deletes the agent on the Wazuh Manager. (Present/Absent) ## Versions diff --git a/WazuhOSSec.psd1 b/WazuhOSSecDSC.psd1 similarity index 97% rename from WazuhOSSec.psd1 rename to WazuhOSSecDSC.psd1 index dd89bbb..d28bacb 100644 --- a/WazuhOSSec.psd1 +++ b/WazuhOSSecDSC.psd1 @@ -1,7 +1,7 @@ # -# Module manifest for module 'MyModule' +# Module manifest for module 'WazuhOSSecDSC' # -# Generated by: Marco +# Generated by: Marco Crank # # Generated on: 10/4/2017 # @@ -9,7 +9,7 @@ @{ # Script module or binary module file associated with this manifest. - RootModule = 'WazuhOSSec.psm1' + RootModule = 'WazuhOSSecDSC.psm1' # Version number of this module. ModuleVersion = '1.0.0' @@ -101,7 +101,7 @@ # LicenseUri = '' # A URL to the main website for this project. - ProjectUri = 'https://github.com/LeanKit-Labs/WazuhOSSec' + ProjectUri = 'https://github.com/LeanKit-Labs/WazuhOSSecDSC' # A URL to an icon representing this module. # IconUri = '' diff --git a/WazuhOSSec.psm1 b/WazuhOSSecDSC.psm1 similarity index 67% rename from WazuhOSSec.psm1 rename to WazuhOSSecDSC.psm1 index 60eb8d9..583a45e 100644 --- a/WazuhOSSec.psm1 +++ b/WazuhOSSecDSC.psm1 @@ -194,9 +194,15 @@ class WazuhAgentRegister [DscProperty(NotConfigurable)] [bool]$AgentRegistered + [DscProperty(NotConfigurable)] + [bool]$AgentRegisterExisting + [DscProperty(NotConfigurable)] [AgentStatus]$AgentStatus + [DscProperty(NotConfigurable)] + [string]$AgentIDFromAPI + [WazuhAgentRegister] Get() { #Set Certificate policy to ignore Self Signed Certs, False by default. @@ -205,30 +211,47 @@ class WazuhAgentRegister Write-Verbose "Allowing Self Signed Certs" $this.IgnoreSelfSignedCerts() } - Write-Verbose "Agent Name: $($this.AgentName)" $this.BaseUrl = "https://" + $this.WazuhServerApiFqdn + ":" + $this.WazuhServerApiPort - Write-Verbose "Base URL: $($this.BaseUrl)" - $this.AgentPath = $this.GetAgentPath() - Write-Verbose "Agent Path: $($This.AgentPath)" - $this.AgentConfigFile = $this.AgentPath + "\" + $this.AgentConfigFile - Write-Verbose "OSSec Agent Config: $($This.AgentConfigFile)" - $this.WazuhServerApiIP = $this.GetWazuhServeIP() - Write-Verbose "Wazuh Server IP: $($This.WazuhServerApiIP)" - - # This block uses the ApiPollingInterval value to determine if it should poll for Agent Registration. - # We put this in to alleviate unnecessary API calls to the server. Other wise every time DSC ran this would make a call - # to the API to verify the Agent was registered. Most of which would return back $true. - # If no ApiPollingInterval is set it wll poll each time - if (($this.ApiPollingInterval -eq 0) -or (($this.InitializePolling()) -and ($this.ApiPollingInterval -ne 0))) - { - #If PollingInterval is 0 cleanup the polling file so we don't have any lingering data lying around should the interval change later - if (($this.ApiPollingInterval -eq 0) -and (Test-Path ($this.AgentPath + "\DSC_Polling.log") -PathType Leaf)) + + if ($this.Ensure -eq [Ensure]::Present) + { + $this.AgentPath = $this.GetAgentPath() + $this.AgentConfigFile = $this.AgentPath + "\" + $this.AgentConfigFile + $this.WazuhServerApiIP = $this.GetWazuhServeIP() + Write-Verbose "Agent Name: $($this.AgentName)" + Write-Verbose "Base URL: $($this.BaseUrl)" + Write-Verbose "Agent Path: $($This.AgentPath)" + Write-Verbose "OSSec Agent Config: $($This.AgentConfigFile)" + Write-Verbose "Wazuh Server IP: $($This.WazuhServerApiIP)" + + # This block uses the ApiPollingInterval value to determine if it should poll for Agent Registration. + # We put this in to alleviate unnecessary API calls to the server. Other wise every time DSC ran this would make a call + # to the API to verify the Agent was registered. Most of which would return back $true. + # If no ApiPollingInterval is set it wll poll each time + if (($this.ApiPollingInterval -eq 0) -or (($this.InitializePolling()) -and ($this.ApiPollingInterval -ne 0))) + { + #If PollingInterval is 0 cleanup the polling file so we don't have any lingering data lying around should the interval change later + if (($this.ApiPollingInterval -eq 0) -and (Test-Path ($this.AgentPath + "\DSC_Polling.log") -PathType Leaf)) + { + Write-Verbose "ApiPollingInterval set to 0, Cleaning up Polling Log File" + Remove-Item -Path ($this.AgentPath + "\DSC_Polling.log") -Force + } + $_RegistrationStatus = $this.RegistrationStatus() + $this.AgentRegistered = $_RegistrationStatus.AgentRegistered + $this.AgentRegisterExisting = $_RegistrationStatus.AgentRegisterExisting + } + else { - Remove-Item -Path ($this.AgentPath + "\DSC_Polling.log") -Force + #No need to poll for agent status so assume Registered with the server + $this.AgentRegistered = $true } - $AgentPollResult = $this.GetAgentInfo() + return $this + } + else + { + $_AgentMetaData = $this.GetAgentInfo() | ConvertFrom-Json #If Total Items greater than or equal to 1 the agent should be registered - if (($agentPollResult | ConvertFrom-Json).data.totalitems -ge 1) + if (($_AgentMetaData).data.totalitems -ge 1) { $this.AgentRegistered = $true } @@ -236,34 +259,54 @@ class WazuhAgentRegister { $this.AgentRegistered = $false } + return $this } - else - { - #No need to poll for agent status so assume Registered with the server - $this.AgentRegistered = $true - } - return $this } [bool] Test() { - $this.Get() - if (!($this.AgentRegistered)) + $_Get = $this.Get() + if ($this.Ensure -eq [Ensure]::Present) + { + if ($_Get.AgentRegistered) + { + Write-Verbose "Agent is registered. GOOD JOB!" + return $true + } + Write-Verbose "Agent is not registered, Begin registration process." + return $false + } + else # Ensure = Absent { + Write-Verbose "Ensure set to `"Absent`", Checking for existing Agent." + if (!($_Get.AgentRegistered)) + { + Write-Verbose "No Agent found on server." + return $true + } + Write-Verbose "Agent found on server, begin deletion process." return $false } - return $true } [void] Set() { - # Register the Agent, Get the Key from Wazuh Server, Import the Key, update ossec.conf, and restart the Agent service - $AgentRegisterResponse = $this.AgentRegisterNew() - $AgentKeyResponse = $this.GetAgentKey($AgentRegisterResponse) - $this.ImportAgentKey($AgentKeyResponse) - $this.AgentControl([AgentStatus]::Stop) - $this.UpdateConfigFile() - $this.AgentControl([AgentStatus]::Start) + $_Get = $this.RegistrationStatus() + + if ($_Get.AgentRegisterExisting -or ($this.Ensure -eq [Ensure]::Absent)) + { + # If there is an existing Agent, Deleted the old and Re-Register as a new agent + $this.AgentRegisterDelete($this.AgentIDFromAPI) + } + if ($this.Ensure -eq [Ensure]::Present) + { + $_AgentRegisterResponseId = $this.AgentRegisterNew() + $_AgentKeyResponse = $this.GetAgentKey($_AgentRegisterResponseId) + $this.ImportAgentKey($_AgentKeyResponse) + $this.AgentControl([AgentStatus]::Stop) + $this.UpdateConfigFile() + $this.AgentControl([AgentStatus]::Start) + } } #region Helper Methods @@ -284,12 +327,26 @@ class WazuhAgentRegister } } + [string]AgentRegisterDelete($AgentId) + { + Write-Verbose "Deleting Agent from server: $($This.AgentName)" + $ApiResponse = $this.WazuhApiRequest("DELETE", "/agents/$($AgentId)") | ConvertFrom-Json + If ($ApiResponse.error -ne '0') + { + throw "ERROR: $($ApiResponse.message)" + } + else + { + Write-Verbose "Agent Deleted: (Agent - $($this.AgentName)) / (ID - $($AgentId))" + return $AgentId + } + } + [string] GetAgentKey($AgentId) { - # Getting agent key from manager + # Small sleep, experienced a timing issue after registering + Start-Sleep -Seconds 2 Write-Verbose "Retrieving Agent Key from server" - #$response = req -method "GET" -resource "/agents/$($agent_id)/key" | ConvertFrom-Json - #ToDo: I think converFrom-Json on the call lilke above so we don't have to below. $_ApiResponse = $this.WazuhApiRequest("Get", "/agents/$($AgentId)/key") | ConvertFrom-Json If ($_ApiResponse.error -ne '0') { @@ -309,8 +366,9 @@ class WazuhAgentRegister Write-Output "y" | & "$($this.GetAgentPath())\manage_agents.exe" "-i $($AgentKey)" "y`r`n" } - # If UseSelfSignedCerts=$true modify Certificate Policy to allow + [void]IgnoreSelfSignedCerts() + # If UseSelfSignedCerts=$true modify Certificate Policy to allow { add-type @" using System.Net; @@ -492,7 +550,6 @@ class WazuhAgentRegister } } - #The following two methods are used in various other methods so we broke them out to reduce code and make it simpler...we hope. [string]GetWazuhServeIP() { Write-Verbose "Resolving Wazuh Server IP Address" @@ -508,8 +565,9 @@ class WazuhAgentRegister [string]GetAgentPath() { - if ($_AgentPath = (Get-Package -Name "*wazuh*").Meta.Attributes.Get_Item("UninstallString").trim([char]"`"") | Split-Path ) + if ($_AgentPath = (Get-Package -Name "*wazuh*" -ErrorAction SilentlyContinue)) { + $_AgentPath = $_AgentPath.Meta.Attributes.Get_Item("UninstallString").trim([char]"`"") | Split-Path return $_AgentPath } else @@ -518,5 +576,54 @@ class WazuhAgentRegister } } + [hashtable]RegistrationStatus() + { + $_RegistrationStatus = [Hashtable]::new() + $_AgentMetaData = $this.GetAgentInfo() | ConvertFrom-Json + #If Total Items greater than or equal to 1 the agent should be registered + if (($_AgentMetaData).data.totalitems -ge 1) + { + Write-Verbose "Existing Agent found" + # Setting this value here so we can use it in the Set() Method to pull back Keys + $this.AgentIDFromAPI = $_AgentMetaData.data.items.id + #We need Path to Client.keys File C:|Program FIles (x86)\Ossec-agent + if (Test-Path ($this.AgentPath + "\Client.keys")) + { + Write-Verbose "Existing Client.Keys file found" + $_clientKeyFilePath = $this.AgentPath + "\Client.keys" + $_currentID = ((Get-Content -Path $_clientKeyFilePath).Split(' '))[0] + $_currentStatus = ($_AgentMetaData).data.items.status + if ((($this.AgentIDFromAPI) -eq $_currentID) -and ($_currentStatus) -ne "Never connected" ) + { + Write-Verbose "Current Agent ID matches Manager Agent ID and Status is Active or Disconnected - Assuming Agent Registered" + #Total Items ge 1, There is a CLient.keys file, the Agent ID from API and Client.keys match, and the agent status is disconnected or active + $_RegistrationStatus.add('AgentRegistered', $true) + } + else + { + Write-Verbose "Client.Keys file exists but Agent IDs do not match or Status is `"Never Connected`"" + # Total Items ge 1, there is a CLient.keys file, and Status is "Never Connected" + # Use the "Insert" API to re-use the Agent ID + $_RegistrationStatus.add('AgentRegistered', $false) + $_RegistrationStatus.add('AgentRegisterExisting', $true) + } + } + else + { + Write-Verbose "No Client.Keys file exists, assuming not registered" + #Total Items ge 1, There is no CLient.keys file + # Use the "Insert" API to re-use the Agent ID + $_RegistrationStatus.add('AgentRegistered', $false) + $_RegistrationStatus.add('AgentRegisterExisting', $true) + } + } + else + { + Write-Verbose "No Agent found on Manager, Agent not registered" + $_RegistrationStatus.add('AgentRegistered', $false) + } + Return $_RegistrationStatus + } + #endregion } \ No newline at end of file