This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Sophos XG API / Lets Encrypt / PowerShell 7 / WAF Update

Hopefully this can help others.

I'm running the home licensed version and just recently moved to v19

I have a few WAF's that are configured externally this script is to do the following.

  • Renew Multiple certificates that are already configured in LE
  • Install CA
  • Install cert
  • Update WAF with new cert
  • Move WAF back into FirewallRuleGroup (I have 1 that is a catch all for WAFs call WAFs :P)
  • Delete Old Certificates - might leave this one out on my final run
  • Schedule a run daily

Prerequisites

  • WebServer/Host that is able to validate the domains for LE
  • Sophos XG WAFs with Path-specific routing Rules
    • Your WAF for the domain name - Protected Servers - Path-specific routing including '/.well-known/acme-challenge/' pointing to your Webserver/Host that is managing the certificate.
  • Local Service Account on Sophos XG with the Required rights.
    • Backup & Firmware/API - Enable and Set allowed IPs
    • Required Security Profile to update configuration
    • Setting Secure String for password on Service Account - https://www.devglan.com/online-tools/aes-encryption-decryption 
      • Enter Password into AES Online Encryption
      • Mode CBC
      • Key Size 128
      • Enter Secret Key as Th1s1Ss1mPlygR8A
      • Select Output as HEX
      • Press Encrypt, The AES Encrypted Output should be correct to use in the place of password within the script.

Some notes that might help others, I've also put some notes in the script to help others with that its doing, not the prettiest but does a job :p

  • <Get><FirewallRule></FirewallRule></Get>, it was useful using <Get> to get the current xml configuration first to understand what was needed, ran into issues where I was putting the xml together and it would fail because half the stuff was missing - wasn't sure if it was a case you can just leave stuff out and it would ignore it (not the case).
    • Towards the end found that pulling the xml in and modifying what was there was a better option and then just transforming it back into the API request.

#Manual Request for Certificates Handled As a Manual Process

#Vars
$DateMonthFormat = (Get-Date -Format MM_yy)
$uri = "https://<sophosxg>:4444/webconsole/APIController"

#PasswordState Vars
$apikey = ""
$PasswordStateServer = ""
$PasswordID = ""
$SearchURI = $PasswordStateServer + "/api/passwords/" + $PasswordID + "?apikey=" + $APIKey
$JsonObject = Invoke-WebRequest -Uri $SearchURI
$Username = ($JsonObject.Content | ConvertFrom-Json).username
$Password = ($JsonObject.Content | ConvertFrom-Json).GenericField1 #Encypted Password

#Get Current Certificates
$Orders = Get-PAOrder -List
$Orders = $Orders | Where-Object { $_.name -match "<yourfilter>" } 

#Used for Cleanup
$ArrWAFObj = @()
$ArrEXPCertObj = @()

foreach ($Order in $Orders)
{
	#Renew Certificate
	Get-PAOrder -MainDomain $Order.MainDomain | Submit-Renewal -WarningAction Stop #Stops Script Running if the Renewal is not required (for scheduled task to run daily)

    #Install Chain Certifcate Sophos XG
    $CACertName = "R3"
    $CertFilename = "fullchain.pfx"
    
    $request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Set operation=`"add`"><Certificate><Action>UploadCertificate</Action><Name>$CACertName</Name><CertificateFormat>pkcs12</CertificateFormat><CertificateFile>$CertFilename</CertificateFile><Password>poshacme</Password></Certificate></Set></Request>"
    
    $CertfileProd = (Get-ChildItem $Order.Folder -Filter $CertFilename)[0].fullname
    $Form = @{
        reqxml = $request
        $((Get-Item $CertfileProd).Basename) = Get-Item -Path $CertfileProd
    }
    $Result = Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck
    $StatusCode = $Result.Response.Certificate.Status
    
    if ($StatusCode.Code -eq "200")
    {
        #Cleanup R3 Cert that is installed along with CA
        $request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Remove><Certificate><Name>$CACertName</Name></Certificate></Remove></Request>"
        $Form = @{
            reqxml = $request
        }
        $Result = Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck
        $Statuscode = $Result.Response.Certificate.Status
    }
    
    if ($StatusCode -ne "200")
    {
        #Install New Certificate on Sophos XG
        $CertPreFix = $DateMonthFormat + "_"
        $CertPostFix = "_LE"
        $CertCN = $Order.FriendlyName
        $CertName = $CertPreFix + $CertCN + $CertPostFix
        
        $request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Set operation=`"add`"><Certificate><Action>UploadCertificate</Action><Name>$CertName</Name><CertificateFormat>pkcs12</CertificateFormat><CertificateFile>cert.pfx</CertificateFile><Password>poshacme</Password></Certificate></Set></Request>"
        $PFXfileProd = (Get-ChildItem $Order.Folder -Filter cert.pfx)[0].fullname
        $Form = @{
            reqxml = $request
            $((Get-Item $PFXfileProd).Basename) = Get-Item -Path $PFXfileProd
        }
        $Result = Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck
    }

	#Get WAF with matching Domain/Certificate CN
	$request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Get><FirewallRule></FirewallRule></Get></Request>"
	
	$Form = @{
		reqxml = $request
	}
	
	$Result = (Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck)

	$Result.Response.FirewallRule | Where-Object PolicyType -eq 'HTTPBased' | ForEach-Object {
		$Domain = $_.HTTPBasedPolicy.Domains.Domain

        #Cycle through each matching domain/cert that replace certificate
		if ($Domain -eq $CertCN)
		{
			$ArrWAFObj += $_.name
			$ArrEXPCertObj += $_.HTTPBasedPolicy.Certificate
			$XmLObj = $_
			[String]$XmL = $XmLObj.OuterXml -replace ' transactionid\=\"\"', '' -replace "\<Certificate\>*.*\<\/Certificate\>", "<Certificate>$CertName</Certificate>" # Update xml with new certificate name.
			
			$PrefixSetReq = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Set operation=`"Update`">"
			$PostfixSetReq = "</Set></Request>"
			$request = $PrefixSetReq + $XmL + $PostfixSetReq
			
			$Form = @{
				reqxml = $request
			}
			
			$Result = (Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck)
			$StatusCode = $Result.Response.FirewallRule.Status			
		}
	}
}


#Move Rule back to WAF FirewallRuleGroup
$request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Get><FirewallRuleGroup></FirewallRuleGroup></Get></Request>"
			
$Form = @{
    reqxml = $request
}

$Result = (Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck)

($Result.Response.FirewallRuleGroup | Where-Object Name -eq 'WAFs') | ForEach-Object {
    $XmLObj = $_
    [String]$XmL = $XmLObj.OuterXml -replace ' transactionid\=\"\"', ''
    [xml]$xml = $XmL
    
    Foreach ($WAF in $ArrWAFObj)
    {
        $Item = Select-Xml -xml $XmL -XPath '//FirewallRuleGroup/SecurityPolicyList/SecurityPolicy[1]'
        $newnode = $item.Node.CloneNode($true)
    

        $newnode.'#text' = $WAF
        $SecurityPolicyList = Select-Xml -Xml $xml -XPath '//FirewallRuleGroup/SecurityPolicyList'
        $SecurityPolicyList.node.AppendChild($newnode)
    }
    
    [String]$xml = $xml.OuterXml
    
    $PrefixSetReq = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Set operation=`"Update`">"
    $PostfixSetReq = "</Set></Request>"
    $request = $PrefixSetReq + $XmL + $PostfixSetReq
    
    $Form = @{
        reqxml = $request
    }
    
    $Result = (Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck)
    $StatusCode = $Result.Response.FirewallRuleGroup.Status
}

# Delete Old Certificate # Can skip this - Cert migth be used elsewhere and could fail - also maybe good to be able to fallback to old cert.
$EXPCerts = $ArrEXPCertObj | Select-Object -Unique

foreach ($ExpCert in $EXPCerts){
    $request = "<Request><Login><Username>$Username</Username><Password passwordform=`"encrypt`">$Password</Password></Login><Remove><Certificate><Name>$ExpCert</Name></Certificate></Remove></Request>"
    $Form = @{
        reqxml = $request
    }
    $Result = Invoke-RestMethod -Uri $Uri -form $form -SkipCertificateCheck
    $Result.Response.Certificate.Status   
}



This thread was automatically locked due to age.