pfSense.psm1
#requires -Version 3.0 <# .Synopsis pfSense management functions built for pfSense version 2.x .DESCRIPTION Haven't been able to find another API, or command line management for pfSense .NOTES It runs on Linux guys.... there shouldn't be a need for these functions... .COMPONENT Security, Networking, Firewall .FUNCTIONALITY pfSense task automation and scriptability #> #region Verion Info <# Version 0.1 - Day one - it's my birfday! Version 0.2 - Function (Export-pfSenseUserCert) : changed : Parameter validation updated. - Function (Connect-pfSense) : changed : Added ability to ignore certificate errors #> #endregion #region Prerequisites # All modules require the core If (!(Get-Module -Name core)) { Try { Import-Module -Name 'core' -ErrorAction Stop } Catch { Try { $uriCoreModule = 'https://raw.githubusercontent.com/masters274/Posh_Repo/master/Modules/Core/core.psm1' $moduleCode = (Invoke-WebRequest -Uri $uriCoreModule -UseBasicParsing).Content Invoke-Expression -Command $moduleCode } Catch { Write-Error -Message ('Failed to load {0}, due to missing core module' -f $PSScriptRoot) } } } #endregion #================================================= MEAT! =========================================================# #region Connection functions Function Connect-pfSense { <# .DESCRIPTION Authenticates to a pfSense server and returns the session variable #> [CmdLetBinding()] Param ( [Parameter( Mandatory=$true, Position=0, HelpMessage='Hostname of pfSesense server' )] [Alias('HostName')] [String] $Server, [Parameter( Mandatory=$true, Position=1, HelpMessage='Credentials for administering pfSense' )] [PSCredential] $Credential, [Switch] $NoTLS, # Not recommended [Switch] $IgnoreCertificateErrors ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent # pfSense requires TLS1.2 This is not an available security protocol in Invoke-WebRequest by default If ([Net.ServicePointManager]::SecurityProtocol -notmatch 'TLS12' -and -not $NoTLS) { [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::TLS12 } <# .NOTE: might be a good idea to add this to your $profile. Default is SSLv3 for Posh web commands!!! # Security protocols for web calls. removes SSL3 and TLS1.0 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::TLS11 [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::TLS12 ...Just a suggestion #> If ($IgnoreCertificateErrors) { Try { Add-Type -TypeDefinition @' using System.Net; using System.Security.Cryptography.X509Certificates; public class InSecureWebPolicy : ICertificatePolicy { public bool CheckValidationResult(ServicePoint sPoint, X509Certificate cert,WebRequest wRequest, int certProb) { return true; } } '@ } Catch {} $pol = [System.Net.ServicePointManager]::CertificatePolicy [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName InSecureWebPolicy <# .NOTE: There is a timeout value to using this option. At the end of this function the policy is returned to its original configuration. PowerShell takes a little time, almost like cache, to recognize the reversion. Therefore this option is only good for fast scripting, and not for coding on the command line. It is recommended that you import the cert into your trusted certificates store #> } } Process { # Variables $uri = 'https://{0}/index.php' -f $Server $pfWebSession = $null $retObject = @() $dictOptions = @{ host=$Server NoTLS=$([bool] $NoTLS) } If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() $request = iwr -Uri $uri $webCredential = @{login='Login' usernamefld=$Credential.GetNetworkCredential().UserName passwordfld=$Credential.GetNetworkCredential().Password __csrf_magic=$($request.InputFields[0].Value) } Invoke-WebRequest -Uri $uri -Body $webCredential -Method Post -SessionVariable pfWebSession | Out-Null $retObject += $pfWebSession $retObject += $dictOptions $retObject } End { [System.Net.ServicePointManager]::CertificatePolicy = $pol } } #endregion #region User functions Function Add-pfSenseUser { <# .Synopsis Adds a new user via pfSense user management page .DESCRIPTION Great for automating the turn up of new remote users .EXAMPLE $Creds = Get-Credential $pfs = Connect-pfSense -Server firewall.local -Credential $Creds Add-pfSenseUser -Session $pfs -Server firewall.local -UserName 'player1' -Password 'MySecretPassword' -FullName 'Player One' Creates a user account on the pfSense firewall named "firewall.local" .NOTES For the certificate, you'll need to get the CA's reference ID. This is located in the page source code of either the CA itself, or on the Add User Management Page. This can be found by visiting one of these pages, right-click and select view page source, the perfrom a search for caref. I'll write something to get this later... an example of this: 4813b1f414fec <div> <select class="form-control" name="caref" id="caref"> <option value="4813b1f414fec">pfSenseCertificateAuthority</option> </div> #> [CmdLetBinding()] [CmdletBinding(DefaultParameterSetName='NoCert')] Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Mandatory=$true, Position=1, HelpMessage='User name' )] [String] $UserName, [Parameter(Mandatory=$true, Position=2, HelpMessage='Password for the user' )] [String] $Password, [Parameter(Mandatory=$true, Position=3, HelpMessage='Display name for the user' )] [String] $FullName, [Parameter(ParameterSetName='Certificate')] [Switch] $Certificate, [Parameter(Mandatory=$false,ParameterSetName="NoCert")] [Parameter(Mandatory=$true,ParameterSetName="Certificate", HelpMessage='Name of the CA' )] [String] $CA, [Int] $KeyLength = 2048, [Int] $LifeTime = 3650, [Switch] $Quiet # No output upon completion ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent } Process { # Variables $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $uri = 'https://{0}/system_usermanager.php' -f $Server If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # pfSense requires a lot of magic.... ++ foreach POST $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession $dictPostData = @{ __csrf_magic=$($request.InputFields[0].Value) usernamefld=$UserName passwordfld1=$Password passwordfld2=$Password descr=$FullName utype='user' save='Save' } # Change the utype to 'system' to create a protected system user $dictCertData = @{ # Extra form fields when requesting a certificate for the user showcert='yes' name="$($UserName)_cert" caref=$CA keylen=$KeyLength lifetime=$LifeTime } If ($Certificate) # Should we request a cert from the CA? { $dictPostData += $dictCertData } # submit/post the form to the server $uri += '?act=new' Invoke-DebugIt -Console -Message '[INFO]' -Value ('Post URI: {0}' -f $uri) Try { $rawRet = Invoke-WebRequest -Uri $uri -Method Post -Body $dictPostData -WebSession $webSession -EA Stop | Out-Null If ($rawRet.StatusCode -eq 200 -and -not $Quiet) { Invoke-DebugIt -Console -Message 'Success' -Force -Color 'Green' ` -Value ('User: {0}, created successfully!' -f $FullName) } } Catch { Write-Error -Message 'Something went wrong submitting the form' } } End { } } Function Get-pfSenseUser { [CmdLetBinding()] Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Position=1)] [String] $UserName, [Switch] $CertInfo, [Switch] $Detail ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent Function Script:Where-Deleteable { param ( [Object] [Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage="Data to filter")] $InputObject ) process { if ($InputObject.title -match 'Delete user') { $InputObject } } } Function Script:Decrypt-pfSenseBackupFile { Param ( $file, [String] $Password ) # Get content of the file # Decrypt the data # Convert from Base64 # Return the clear-text form contents as and XML object } } Process { # Variables $objUsers = @() $objUsersDetail = @() #--------------------------------------------------------------------------------------# $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $uri = 'https://{0}/system_usermanager.php' -f $Server If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # pfSense requires a lot of magic.... ++ foreach POST $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession # Get a list of deletable users. $users = $request.Links | Where-Deleteable # Note: can't delete yourself # Build an array with usernames and IDs, which can be deleted by the current user. Foreach ($user in $users) { $uname = $user.href.Split(';').Replace('&','').Trim() -match 'username' $uid = $user.href.Split(';').Replace('&','').Trim() -match 'userid' $objBuilder = New-Object -TypeName PSObject $objBuilder | Add-Member -MemberType NoteProperty -Name 'Username' -Value $($uname.Split('=')[1]) $objBuilder | Add-Member -MemberType NoteProperty -Name 'UserID' -Value $($uid.Split('=')[1]) If ($CertInfo) { $userEditUri = $uri + ('?act=edit&userid={0}' -f $($uid.Split('=')[1])) $userReq = Invoke-WebRequest -Uri $userEditUri -WebSession $webSession -Method Get $cert = $userReq.ParsedHtml.frames.document.body.outerHTML.Split("`n") | Where-Object {$_ -match "Remove this certificate association"} If ($cert) { #$certName = '' $boolCert = $true } Else { #$certName = $null $boolCert = $false } $objBuilder | Add-Member -MemberType NoteProperty -Name 'Cert' -Value $boolCert #$objBuilder | Add-Member -MemberType NoteProperty -Name 'CertName' -Value $certName } $objUsers += $objBuilder } If ($Detail) { # Haven't found a good way to get user details without the backup file... didn't want to do this # DEPRICATED - TODO: Check if OpenSSL is installed, if so download the backup file encrypted with random password # DEPRICATED - TODO: use [System.Security.Cryptography.AESManaged] to decrypt the config file # TODO: Create the XML object in variable. Do not save file to disk. $tempFile = $env:TEMP + '\' + [guid]::NewGuid().guid + '.xml' Backup-pfSenseConfig -Session $Session -FilePath $tempFile [xml] $xmlFile = Get-Content -Path $tempFile -Encoding Ascii Remove-Item -Path $tempFile -Force # Don't need this just laying around. Foreach ($user in $xmlFile.pfsense.system.user) { # Cert info if exists $objCert = $xmlFile.pfsense.cert | ? {$_.refid -eq $user.cert} $objCA = $xmlFile.pfsense.ca | ? {$_.refid -eq $objCert.caref} $uid = $objUsers | ? {$_.username -eq $user.name} | %{$_.userid} $objCrl = $xmlFile.pfsense.crl | ? {$_.caref -eq $objCA.refid} $objBuilder = New-Object -TypeName PSObject $objBuilder | Add-Member -MemberType NoteProperty -Name 'Username' -Value $user.name $objBuilder | Add-Member -MemberType NoteProperty -Name 'System_UID' -Value $user.uid $objBuilder | Add-Member -MemberType NoteProperty -Name 'UserID' -Value $uid $objBuilder | Add-Member -MemberType NoteProperty -Name 'Expiration' -Value $user.expires $objBuilder | Add-Member -MemberType NoteProperty -Name 'User_Type' -Value $user.scope $objBuilder | Add-Member -MemberType NoteProperty -Name 'Cert' -Value $objCert.descr.'#cdata-section' $objBuilder | Add-Member -MemberType NoteProperty -Name 'Cert_ID' -Value $user.cert $objBuilder | Add-Member -MemberType NoteProperty -Name 'CA' -Value $objCA.descr.'#cdata-section' $objBuilder | Add-Member -MemberType NoteProperty -Name 'CA_ID' -Value $objCA.refid $objBuilder | Add-Member -MemberType NoteProperty -Name 'CRL' -Value $objCrl.descr.'#cdata-section' $objBuilder | Add-Member -MemberType NoteProperty -Name 'CRL_ID' -Value $objCrl.refid $objUsersDetail += $objBuilder } If ($Username) { Try { $objUsersDetail | Where-Object {$_.Username -eq $UserName} } Catch { Write-Host -ForegroundColor Red "Username $UserName not found" $objUsersDetail } } Else { $objUsersDetail } } Else { If ($Username) { Try { $objUsers | Where-Object {$_.Username -eq $UserName} } Catch { Write-Host -ForegroundColor Red "Username $UserName not found" $objUsers } } Else { $objUsers } } } End { } } Function Remove-pfSenseUser { [CmdLetBinding()] Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Mandatory=$true, Position=1, HelpMessage='User name' )] [String] $UserName, [Switch] $RevokeCert ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent } Process { # Variables $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $uri = 'https://{0}/system_usermanager.php' -f $Server If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # pfSense requires a lot of magic.... ++ foreach POST $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession # Get a list of deletable users. $objUser = Get-pfSenseUser -Session $Session -Detail -UserName $UserName # Get the ID of the username to be deleted. Try { [bool] (!($objUser.UserID -eq $null)) Invoke-DebugIt -Console -Message '[INFO]' -Value ('User ID found: {0}' -f $objUser.UserID) } Catch { Write-Error -Message ` 'Failed to get the user ID for the username provided. Check the username, and try again' return } If ($RevokeCert) { Revoke-pfSenseUserCert -Session $Session -UserName $UserName -Reason 'Cessation of Operation' } # Dictionary submitted as body in our POST request $dictPostData = @{ __csrf_magic=$($request.InputFields[0].Value) 'delete_check[]'=$($objUser.UserID) 'dellall'='dellall' } Try { $rawRet = Invoke-WebRequest -Uri $uri -Method Post -Body $dictPostData -WebSession $webSession -EA Stop | Out-Null } Catch { Write-Error -Message 'Something went wrong submitting the form' } } End { } } Function Export-pfSenseUserCert { [CmdLetBinding()] Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Mandatory=$true, Position=1, HelpMessage='User name' )] [String] $UserName, [Parameter(Position=2)] [ValidateSet('Cert','Key','P12')] [String] $CertAction = 'Cert', [Parameter(Position=3)] [ValidateScript({ try { $Folder = Get-Item $($_ |Split-Path -Parent) -ErrorAction Stop } catch [System.Management.Automation.ItemNotFoundException] { Throw [System.Management.Automation.ItemNotFoundException] "${_} Maybe there are network issues?" } if ($Folder.PSIsContainer) { $True } else { Throw [System.Management.Automation.ValidationMetadataException] "The path '${_}' is not a container." } })] [String] $FilePath ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent } Process { # Variables $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $uri = 'https://{0}/system_certmanager.php' -f $Server If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # Get a list of deletable users. $objUsers = Get-pfSenseUser -Session $Session # Get the ID of the username to be deleted. Try { $userID = $objUsers | Where-Object {$_.Username -eq $UserName} | ForEach-Object {$_.UserID} Invoke-DebugIt -Console -Message '[INFO]' -Value ('User ID found: {0}' -f $userID) } Catch { Write-Error -Message ` 'Failed to get the user ID for the username provided. Check the username, and try again' return } Switch ($CertAction) { Key { $uri += ('?act=key&id={0}' -f $userID) $fExt = 'key' Break } P12 { $uri += ('?act=p12&id={0}' -f $userID) $fExt = 'p12' Break } Default { $uri += ('?act=exp&id={0}' -f $userID) $fExt = 'crt' Break } } If (!$FilePath) { [String] $FilePath = ('{0}\{1}_pfSenseUserCertificate.{2}' -f $($PWD.Path), $UserName, $fExt) } Invoke-DebugIt -Console -Message '[INFO]' -Value ('Export path = {0}' -f $FilePath) -Force Invoke-DebugIt -Console -Message '[INFO]' -Value ('URI = {0}' -f $uri.ToString()) $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession ConvertFrom-HexToFile -HexString $request.Content -FilePath $FilePath } End { } } Function Revoke-pfSenseUserCert { Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Mandatory=$true, Position=1, HelpMessage='User name' )] [String] $UserName, [ValidateSet('No Status (default)', 'Unspecified', 'Key Compromise', 'CA Compromise', 'Affiliation Change', 'Superseded', 'Cessation of Operation', 'Certificate Hold' )] [String] $Reason = 'Unspecified' ) Begin { } Process { # Variables $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $user = Get-pfSenseUser -Session $Session -Detail -UserName $UserName $dictReason = @{ 'No Status (default)' = '-1' 'Unspecified' = 0 'Key Compromise' = 1 'CA Compromise' = 2 'Affiliation Changed' = 3 'Superseded' = 4 'Cessation of Operation' = 5 'Certificate Hold' = 6 } If ($user.count -gt 1 -or $user -eq $null) { Write-Error -Message ('Failed to get username {0}' -f $UserName) Return } If (!$user.CRL_ID) { Write-Error -Message ('No CRL for {0}' -f $UserName) Return } $uri = 'https://{0}/system_crlmanager.php?act=edit&id={1}' -f $Server, $user.CRL_ID If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # pfSense requires a lot of magic.... ++ foreach POST $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession # Dictionary submitted as body in our POST request $dictPostData = @{ __csrf_magic=$($request.InputFields[0].Value) certref=$($user.Cert_ID) crlreason=$($dictReason["$Reason"]) submit='Add' id=$($user.CRL_ID) act='addcert' crlref=$($user.CRL_ID) } Try { $rawRet = Invoke-WebRequest -Uri $uri -Method Post -Body $dictPostData -WebSession $webSession -EA Stop | Out-Null } Catch { Write-Error -Message 'Something went wrong submitting the form' } } End { } } Function Restore-pfSenseUserCert { <# Un-Revoke: Remove a user's certificate from a CRL #> Param ( [Parameter(Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Mandatory=$true, Position=1, HelpMessage='User name' )] [String] $UserName ) Begin { } Process { } End { } } #endregion #region System functions Function Backup-pfSenseConfig { <# .Synopsis Backup your pfSense firewall .DESCRIPTION Long description .EXAMPLE $Creds = Get-Credential $pfs = Connect-pfSense -Server firewall.local -Credential $Creds Backup-pfSenseConfig -Server firewall.local -Session $pfs #> [CmdLetBinding()] Param ( [Parameter( Mandatory=$true, Position=0, HelpMessage='Valid/active websession to server' )] [PSObject] $Session, [Parameter(Position=1)] [ValidateScript({ try { $Folder = Get-Item $($_ | Split-Path -Parent) -ErrorAction Stop } catch [System.Management.Automation.ItemNotFoundException] { Throw [System.Management.Automation.ItemNotFoundException] "${_} Maybe there are network issues?" } if ($Folder.PSIsContainer) { $True } else { Throw [System.Management.Automation.ValidationMetadataException] "Invalid path '${_}'." } })] [String] $FilePath = ('{0}\{1}_pfSenseBackup.xml' -f $($PWD.Path), $(Get-Date -UFormat '%Y%m%d_%H%M%S')), [Parameter(Position=2)] [String] $EncryptPassword ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent } Process { # Variables $Server = $Session.host [bool] $NoTLS = $Session.NoTLS [Microsoft.PowerShell.Commands.WebRequestSession] $webSession = $Session[0] $uri = 'https://{0}/diag_backup.php' -f $Server If ($NoTLS) # highway to tha Danger Zone!!! { $uri = $uri -Replace "^https:",'http:' Invoke-DebugIt -Console -Message '[WARNING]' -Value 'Insecure option selected (no TLS)' -Color 'Yellow' } Invoke-DebugIt -Console -Message '[INFO]' -Value $uri.ToString() # pfSense requires a lot of magic.... ++ foreach POST $request = Invoke-WebRequest -Uri $uri -Method Get -WebSession $webSession $dictPostData = @{ __csrf_magic=$($request.InputFields[0].Value) donotbackuprrd='yes' download='Download configuration as XML' } If ($EncryptPassword) { $dictSecurity = @{ encrypt_password="$EncryptPassword" encrypt="yes" } $dictPostData += $dictSecurity Invoke-DebugIt -Console -Message '[INFO]' -Value 'Encryption password set' } Try { $rawRequest = Invoke-WebRequest -Uri $uri -Method Post -Body $dictPostData -WebSession $webSession -EA Stop } Catch { Write-Error -Message 'Something went wrong submitting the form' } If ($rawRequest) { Invoke-DebugIt -Console -Message '[INFO]' -Value ('Output file: {0}' -f $FilePath) ConvertFrom-HexToFile -HexString $rawRequest.Content -FilePath $FilePath } Else { Write-Error -Message 'Failed to read the output file' } } End { } } Function Restore-pfSenseConfig { } Function Add-pfSenseStaticRoute { } Function Get-pfSenseStaticRoute { } Function Remove-pfSenseStaticRoute { } Function Add-pfSenseGateway { } Function Get-pfSenseGateway { } Function Remove-pfSenseGateway { } #endregion #region Firewall functions Function Add-pfSenseFirewallRule { } Function Get-pfSenseFirewallRule { } Function Remove-pfSenseFirewallRule { } Function Add-pfSenseNatRule { } Function Get-pfSenseNatRule { } Function Remove-pfSenseNatRule { } #endregion |