Hi, this script downloads the full Microsoft Endpoint List from their API and then adds addresses and optionally URLs to both ipv4 and optionally ipv6 address lists. If URLs are enabled wildcard URLs are filtered out as they can’t be supported by address lists, but the base URLs are preserved. This probably means some endpoints will be missed but it’s proven adequate for the policy based routing I needed it for.
List info here: https://learn.microsoft.com/en-gb/microsoft-365/enterprise/microsoft-365-ip-web-service?view=o365-worldwide
I had hoped someone would already have made this and done a better job but I couldn’t find it.
I expect some of the regular faces can clear up my code into something much nicer!
Be sure to get your own GUID from here: https://www.guidgenerator.com/
Set it in the “clientRequestID” variable at the top before running it.
Tested on ROS v7.15
#Add as a system or scheduled script. To run manually and monitor output use: /system scripts run "scriptname"
#Downloads the latest endpoint list from Microsoft, isolates IP prefixes and URLs from the JSON response then adds them as address list entries for IPv4 and IPv6.
#Removes old addresses no longer present on the list without spamming the log.
#Wildcard URLs are filtered out as they can't be supported by address lists, but the base URLs are preserved.
#Tested on ROS v7.15
:local urlBase "https://endpoints.office.com/endpoints/worldwide"
:local clientRequestID "IMPORTANT-PUT-YOUR-GUID-HERE"; #Go here to generate a GUID: "https://www.guidgenerator.com/"
:local result [/tool fetch url=($urlBase."?clientrequestid=".$clientRequestID) as-value output=user];
:local doIPv6 "yes"; #yes/no to do or skip the IPv6 address list.
:local doURLs "yes"; #yes/no to do or skip URl processing.
:local addressListName "microsoft"
:local currentIpv4AddressList [/ip firewall address-list print as-value where list=$addressListName]
:local currentIpv6AddressList [/ipv6 firewall address-list print as-value where list=$addressListName]
:local newIpv4AddressList [:toarray ""]
:local newIpv6AddressList [:toarray ""]
#add new addresses
:if ( $result->"status" = "finished" ) do={
:local data [:deserialize from=json value=($result->"data")]
:if ($result ~ "ips") do={
:foreach item in=($data) do={
:local comment ""
:if ([:typeof ($item->"serviceAreaDisplayName")] != "nil") do={
:set comment ($item->"serviceAreaDisplayName")
}
#IPs
:if ([:typeof ($item->"ips")] != "nil") do={
:foreach ip in=(($item->"ips")) do={
:local iptype ""
:if ( $ip ~ ":" ) do={ :set iptype "v6"; }
:if ( ($iptype = "v6") and ($doIPv6 = "yes") ) do={:set newIpv6AddressList ( $newIpv6AddressList, $ip )} else {
:if ($ip ~ "/32") do={
:set newIpv4AddressList ( $newIpv4AddressList, [:pick $ip 0 ([:len $ip] - 3)] )
} else {
:set newIpv4AddressList ( $newIpv4AddressList, $ip )
}
}
:if ( $doIPv6 != "yes" ) do={
:if ( $iptype != "v6" ) do={:put ("IP".$iptype.": ".$ip."\t\t\t\t".$comment)}
:set iptype ""
} else {
:put ("IP".$iptype.": ".$ip."\t\t\t\t".$comment)
}
:do { [:parse "/ip$iptype firewall address-list add list=$addressListName address=$ip comment=\"$comment\""] } on-error={}
:set iptype ""
}
}
#URLs
:if ( ([:typeof ($item->"urls")] != "nil") and ($doURLs = "yes") ) do={
:foreach urlLong in=(($item->"urls")) do={
:local url $urlLong
#Trim off "*."" if present
:if (([:pick $urlLong 0].".") = "*.") do={ :set url [:pick $urlLong 2 ([:len $urlLong])] }
:set newIpv4AddressList ( $newIpv4AddressList, $url )
:if ( $doIPv6 = "yes" ) do={:set newIpv6AddressList ( $newIpv6AddressList, $url )}
:put ("URL: ".$url."\t\t\t\t".$comment)
:do { /ip firewall address-list add list=$addressListName address=$url comment="$comment" } on-error={}
:if ( $doIPv6 = "yes" ) do={ :do { /ipv6 firewall address-list add list=$addressListName address=$url comment="$comment" } on-error={} }
}
}
}
}
};
#spacer
:put ""
:if ( $doURLs != "yes" ) do={:put "URL Processing Turned Off"}
:if ( $doIPv6 != "yes" ) do={:put "IPv6 Address Processing Turned Off"}
#remove old ipv4 addresse
:put "Removing old IPv4 Addresses"
:foreach currentList in=($currentIpv4AddressList) do={
:local found "no"
:foreach newIP in=($newIpv4AddressList) do={
:if ( [:tostr ($currentList->"address")] = [:tostr ($newIP)]) do={ :set found "yes" }
}
:if ($found = "no") do={
:do { /ip firewall address-list remove [find where .id=($currentList->".id") and list=$addressListName] } on-error={}
}
};
#remove old ipv6 addresses
:if ( $doIPv6 = "yes" ) do={
:put "Removing old IPv6 Addresses"
:foreach currentList in=($currentIpv6AddressList) do={
:local found "no"
:foreach newIP in=($newIpv6AddressList) do={
:if ( [:tostr ($currentList->"address")] = [:tostr ($newIP)]) do={ :set found "yes" }
}
:if ($found = "no") do={
:do { /ipv6 firewall address-list remove [find where .id=($currentList->".id") and list=$addressListName] } on-error={}
}
}
} else {
:if ( [:len $currentIpv6AddressList] > 0 ) do={
:put "Removing all IPv6 Addresses"
:do { /ipv6 firewall address-list remove [find where list=$addressListName] } on-error={}
}
};
:put "Done"