API and Powershell usage to recursively get and replace SMTP Relay Server for each site

Due to the lack of a global setting for this topic, I am forced to use the API to change the SMTP Relay Server for each of our 100+ Sites.

I need to recursively find all sites and their ID, which isn’t the issue, but then use the Site ID as a variable to obtain the inventory of the SMTP Relay Server. Those that have not been updated with the latest server name, must be changed to the correct server without affecting other fields.

I use powershell exclusively and without examples to help guide me, I need your help.

  1. Obtain all site IDs and make variables
  2. Use powershell to update only the smtp relay server field.
    I’m thinking something like:
    for each siteid where smtp relay server -neq “xyzserver”, put smtp relay server “abcserver”

Please share your ideas on how to make this work. Your help is appreciated.

Here’s an example in Powershell. You really don’t need to go as far as creating logic to grab out every ID. If you just use a GET and then a PUT on the full list in combination with a text editor you should be fine.

Example GET Request:

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept", "application/json;charset=UTF-8")
$headers.Add("Authorization", "Basic <Base64 encoded credentials>")

$response = Invoke-RestMethod 'https://<your console>:3780/api/3/sites/6/alerts/smtp' -Method 'GET' -Headers $headers
$response | ConvertTo-Json

Output looks like this:

{
“resources”: [
{
“enabled”: true,
“enabledScanEvents”: {
“failed”: false,
“paused”: false,
“resumed”: false,
“started”: true,
“stopped”: false
},
“enabledVulnerabilityEvents”: {
“confirmedVulnerabilities”: true,
“potentialVulnerabilities”: true,
“unconfirmedVulnerabilities”: true,
“vulnerabilitySeverity”: “any_severity”
},
“id”: 3,
“limitAlertText”: false,
“links”: [
{
“href”: “https://<Your Console:3780/api/3/sites/6/alerts/smtp/3”,
“rel”: “self”
}
],
“name”: “Test”,
“notification”: “SMTP”,
“recipients”: [
test@test.com
],
“relayServer”: “2.3.4.5”,
“senderEmailAddress”: “test@test.com
}
],
“links”: [
{
“href”: “https://<Your Console:3780/api/3/sites/6/alerts/smtp”,
“rel”: “self”
}
]
}

From here you could just copy everything within the resources section starting at the first square bracket and ending at the last square bracket before “links”

Paste that into a text editor and just do a find and replace on the relayServer value you’re looking for so in this example it would be 2.3.4.5 and replace all instances with your desired value (my example is 3.4.5.6)

Then just do a PUT like so and copy in your section into the Body:

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json;charset=UTF-8")
$headers.Add("Authorization", "Basic Base64 encoded creds")

$body = "[`n        {`n            `"enabled`": true,`n            `"enabledScanEvents`": {`n                `"failed`": false,`n                `"paused`": false,`n                `"resumed`": false,`n                `"started`": true,`n                `"stopped`": false`n            },`n            `"enabledVulnerabilityEvents`": {`n                `"confirmedVulnerabilities`": true,`n                `"potentialVulnerabilities`": true,`n                `"unconfirmedVulnerabilities`": true,`n                `"vulnerabilitySeverity`": `"any_severity`"`n            },`n            `"id`": 2,`n            `"limitAlertText`": false,`n            `"links`": [`n                {`n                    `"href`": `"https://<Your Console>:3780/api/3/sites/6/alerts/smtp/2`",`n                    `"rel`": `"self`"`n                }`n            ],`n            `"name`": `"Test`",`n            `"notification`": `"SMTP`",`n            `"recipients`": [`n                `"test@test.com`"`n            ],`n            `"relayServer`": `"3.4.5.6`",`n            `"senderEmailAddress`": `"test@test.com`"`n        }`n    ]"

$response = Invoke-RestMethod 'https://<Your Console>:3780/api/3/sites/6/alerts/smtp' -Method 'PUT' -Headers $headers -Body $body
$response | ConvertTo-Json

Let me know if this helps or if there’s anything else you need.

1 Like

Thanks John, this gives me a lot to go on.

I was under the impression that each site id had to be defined first, as in the API instruction (e.g., /api/3/sites/{id}/alerts/smtp).

When you say “full list”, how would I apply to the entire Site list, without defining the sites in an array after extracting them first?

There’s two methods essentially, the way you’re looking at with the Site ID is a sub level of what I’m doing.

Screen Shot 2022-07-08 at 1.04.14 PM

The red box is essentially the level that you’re thinking of where you can specify just one ID at a time. The green box is above the ID level which is just grabbing or putting ALL alerts at once.

Keep in mind that in my method YOU HAVE TO copy all the contents back in. If you were to only copy in the alerts you changed it will remove the other alerts.

Thanks, I was actually looking at the site Id at a higher level in the cyan box. That’s where I was building the logic upon and seems that needs to be in place before I can use the Site SMTP alerts. I think both methods will work, but your second method seems to be more efficient.

image

Actually you are completely right, I didn’t realize I already had a saved value in my request. So what I had shared was specific to one site with the ID of 3.

You’re issue is more complex than I first realized.

Sorry for the misunderstanding. I’m no Powershell wizard but basically what you will want to do is:

Use this to get all of the sites

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept", "application/json;charset=UTF-8")
$headers.Add("Authorization", "Basic <Base64 encoded creds>")

$response = Invoke-RestMethod 'https://<Your Console>:3780/api/3/sites' -Method 'GET' -Headers $headers
$response | ConvertTo-Json

This will give you all of the sites and then you’ll need to use ConvertFrom-Json and Select-Object to grab the specific “id” Property. You can put that into a csv or store it as a variable however you wish.

Once you have all of those variables though, you would move to that next step like you had mentioned and iterate through each of those ids using this query

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept", "application/json;charset=UTF-8")
$headers.Add("", "")
$headers.Add("Authorization", "Basic <Base64 encoded creds>")

$response = Invoke-RestMethod 'https://<Your Console>:3780/api/3/sites/$id/alerts/smtp' -Method 'GET' -Headers $headers
$response | ConvertTo-Json

Notice the id variable in the URL

then take the JSON responses from each and use the same ConvertFrom-Json and Select-Objectto grab only the requests section (stripping the links section) and then use regex to do the find “xyzserver” and replace with “abcserver”.

From there take that edited JSON and use that as the $body variable for the PUT request

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json;charset=UTF-8")
$headers.Add("Authorization", "Basic <Base64 encoded creds>")

$body = "[`n        {`n            `"enabled`": true,`n            `"enabledScanEvents`": {`n                `"failed`": false,`n                `"paused`": false,`n                `"resumed`": false,`n                `"started`": true,`n                `"stopped`": false`n            },`n            `"enabledVulnerabilityEvents`": {`n                `"confirmedVulnerabilities`": true,`n                `"potentialVulnerabilities`": true,`n                `"unconfirmedVulnerabilities`": true,`n                `"vulnerabilitySeverity`": `"any_severity`"`n            },`n            `"id`": 2,`n            `"limitAlertText`": false,`n            `"links`": [`n                {`n                    `"href`": `"https://<Your Console>:3780/api/3/sites/6/alerts/smtp/2`",`n                    `"rel`": `"self`"`n                }`n            ],`n            `"name`": `"Test`",`n            `"notification`": `"SMTP`",`n            `"recipients`": [`n                `"test@test.com`"`n            ],`n            `"relayServer`": `"3.4.5.6`",`n            `"senderEmailAddress`": `"test@test.com`"`n        }`n    ]"

$response = Invoke-RestMethod 'https://<Your Console>:3780/api/3/sites/$id/alerts/smtp' -Method 'PUT' -Headers $headers -Body $body
$response | ConvertTo-Json

.
.
.
So those three requests should get you started, you just need to figure out the Powershell bits to do the iteration and find and replace.

I hope this helps!

1 Like

For the find and replace section you might be able to convert the JSON to a string and simply do a $output.replace(‘xyzserver’,‘abcserver’)

Thanks John, I will give this a shot and appreciate your help.

Disclaimer: This is not a script supported by Rapid7 and comes as is. This is a value add that hopefully helps you accomplish your task

This is in Python which I know is not the original language you were looking for but should do the trick. I was running this on my mac (so you may need to change the shebang) against a lab box that doesn’t have a signed certificate so I was ignoring certificate warnings (you may want to set that up for your use case)

Directions:
Set your values in the console_url and base64_cred variables as well as the current_server_value and new_server_value

The base64 creds should be in the form of username:password before you encode them. So for example “username:password” encoded would be “dXNlcm5hbWU6cGFzc3dvcmQ=”

I hope this helps and works for you!

#!/usr/local/bin/python3

import requests
import json
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

################################
# Specific Variables to your console and desired outcome
console_url = "Console IP:3780"
base64_cred = "Base 64 of credentials"
current_server_value = "1.2.3.4"
new_server_value = "2.3.4.5"

################################
# Initial API call to get all Site IDs
sites_url = "https://"+console_url+"/api/3/sites"
payload={}
headers = {
  'Accept': 'application/json;charset=UTF-8',
  'Authorization': 'Basic '+base64_cred
}
sites_response = requests.request("GET", sites_url, headers=headers, data=payload, verify=False)
json_text = sites_response.text
parsed = json.loads(json_text)

################################
# Takes the Site IDs and puts them into a list to iterate through in the next step
site_ids = []
for ids in parsed['resources']:
	site_ids.append((ids['id']))

################################
# Iterates through each ID and pulls the Alerts configured
for sid in site_ids:
	smtp_url = "https://"+console_url+"/api/3/sites/"+str(sid)+"/alerts/smtp"
	smtp_payload={}
	smtp_headers = {
  	  'Accept': 'application/json;charset=UTF-8',
  	  'Authorization': 'Basic '+base64_cred
	}
	smtp_response = requests.request("GET", smtp_url, headers=smtp_headers, data=smtp_payload, verify=False)
	smtp_text = smtp_response.text
	smtp_json = json.loads(smtp_text)

		
	################################
	# Looks at the JSON output and only moves forward if there is an alert configured for the site
	if not len(smtp_json['resources']) == 0:
		################################
		# Replaces the current SMTP Relay Server value with the new value
		for alert in smtp_json['resources']:
			alert['relayServer'] = alert['relayServer'].replace(current_server_value, new_server_value)

			################################
			# Puts the updated alert into a new variable
			smtp_payload = json.dumps(smtp_json['resources'])
			smtp_put_headers = {
  			'Content-Type': 'application/json',
  			'Accept': 'application/json;charset=UTF-8',
  			'Authorization': 'Basic '+base64_cred
			}

			################################
			# Final PUT API call to update the site with new values
			smtp_put_response = requests.request("PUT", smtp_url, headers=smtp_put_headers, data=smtp_payload, verify=False)
			print(smtp_put_response.text)