Hi,
New to API's and coding in general.
I am trying to write a script that adds a new firewall rule to a network (I'm using a test site atm).
However I am getting a 404 error which I assume means my URL is wrong?
I got this from the Meraki reference guide but I'm not sure its correct.
/networks/{network_id}/appliance/firewall/l3firewallRules
Also - I read somewhere that adding a new rule would overwrite all the existing rules! Is that true and if so is there a way to add a rule so that it doesn't effect any existing rules?
Thanks!
import requests
import security
import json
api_key = security.MERAKI_API_KEY
organizationId = security.ORG_ID
network_id = "xxxxxxxxx"
rule = {
'name': 'Test Rule',
'policy': 'deny',
'protocol': 'any',
'srcPort': 'any',
'srcCidr': '1.1.1.1/24',
'dstPort': 'any',
'dstCidr': '0.0.0.0/0',
}
url = f"https://api.meraki.com/api/v1/networks/{network_id}/appliance/firewall/l3firewallRules"
headers = {
'X-Cisco-Meraki-API-Key': api_key,
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, data=json.dumps(rule), verify=False)
print(response.status_code)
Solved! Go to solution.
HTTP400 is sent because the dashboard doesn't like your request. Doesn't mean you are missing a parameter.
Using the Meraki SDK would be easier to troubleshoot.
The issue is within the payload.
data=json.dumps(rule)
Here is the correct URL:
https://developer.cisco.com/meraki/api/#!update-network-l-3-firewall-rules
Hi,
Thanks for the reply, but Im still getting a 404 error 😞
am I getting the url wrong or can something else cause this issue?
url = f"https://api.meraki.com/api/v1/networks/{network_id}/l3FirewallRules"
response = requests.post(url, headers=headers, data=json.dumps(rule), verify=False)
404 - Not Found The requested resource doesn't exist or incorrect API key
You can test It on the Meraki developer Hub before.
I would troubleshoot this issue with these steps :
1- Create a dummy rule from the dashboard
2- GET the rule via the API
3- PUT the exact same response received from step #2.
You have to be Org admin / Net admin to do that.
huh - if I try to use the PUT operation from the dashboard - its gets a 404 there too
Have you enabled API access?
Yes, as mentioned, moments before i tried the PUT I did a successful GET
I think your link may be the out of date version 0
Version 1
https://developer.cisco.com/meraki/api-v1/#!update-network-appliance-firewall-l-3-firewall-rules
Use this one:
https://developer.cisco.com/meraki/api-latest/
If I run the exact same script as a GET - it works ok. I can also run the get from the dashboard.
I noticed I was using POST rather than PUT so made that change but if I do that I get a 400 error instead 😞
url = f"https://api.meraki.com/api/v1/networks/{network_id}/appliance/firewall/l3FirewallRules"
response = requests.put(url, headers=headers, data=json.dumps(rule), verify=False)
400 - Bad Request The request was unacceptable, often due to missing a required parameter.
shouldn't the dashboard tell me if there are any required parameters?
HTTP400 is sent because the dashboard doesn't like your request. Doesn't mean you are missing a parameter.
Using the Meraki SDK would be easier to troubleshoot.
The issue is within the payload.
data=json.dumps(rule)
Not pretty , but should work.
import os
import json
import requests
import codecs
base_url_v1 = 'https://api.meraki.com/api/v1'
NetworkID = XXXXXXXXXXX
headers = {
'x-cisco-meraki-api-key': format(str(apikey)),
'Content-Type': 'application/json'
}
def __returnhandler(statuscode, returntext):
if str(statuscode) == '200':
return returntext
else:
print('HTTP Status Code: {0}\n'.format(statuscode))
def getL3FirewallRules():
geturl = '{0}/networks/{1}/appliance/firewall/l3FirewallRules'.format(str(base_url_v1), str(NetworkID))
dashboard = requests.get(geturl, headers=headers,verify=False)
result = __returnhandler(dashboard.status_code, dashboard.text)
return result
L3FWRules = getL3FirewallRules()
payload = L3FWRules
url = 'https://api.meraki.com/api/v1/networks/{0}/appliance/firewall/l3FirewallRules'.format(NetworkID)
response = requests.request('PUT', url, headers=headers, data = payload,verify=False)
print(response.text.encode('utf8'))
Thanks you!
This put me on the right track. I changed
data=json.dumps(rule)
to
data=rule,
and then changed the rule paramater to this
rule = '''{
"rules": [
{
"comment": "Test Rule.",
"policy": "allow",
"protocol": "tcp",
"destPort": "443",
"destCidr": "192.168.1.0/24",
"srcPort": "Any",
"srcCidr": "Any",
"syslogEnabled": false
}
]
}'''
(slight syntax change)
just my second issue now - this seems to overwrite all the existing rules rather than just add this to the list.
how do I add things without deleting all the others?
You can't update, you have to set all rules again when you create a new rule.
You can't append a rule. You have to push the whole rule base.
So I would suggest doing a GET first , append your modifications , then PUT the whole rule base.
am I going to have to call the existing rules, then add them along with the new rule to the update call and basically re-write all the rules each time?
https://developer.cisco.com/meraki/build/mx-firewall-control-python-script/
regarding updating the rules and having to get a copy of existing rules and then adding those to the new update request - this is a little terrifying - what if it misses a rule or something, id have no way of checking 😕
I guess I could get it to export a json file after the get request as something I could look at to make sure its all ok right?
Yes. The order of firewall rules is important, and Dashboard can't assume placement. Placing a rule at the end may not actually do anyty, thus it is up to you to place a rule in the correct position.
Another approach to consider is that if you want maintain rules via the API, it may be worth keeping the 'master copy' in a local database, then just write the whole set each time.
Then if you need to check rules, it's an API read and compare against the local copy.
I'm having a problem appending the new rule list to the existing list :(|
I think its because I have created the existing rule list as a dictionary. However if I try to re-write the dictionary as a list (with square brackets [] ) the code stops working. "Invalid syntax"
import requests
import security
import json
# Replace with your Meraki API key and network ID
api_key = security.MERAKI_API_KEY
organizationId = security.ORG_ID
network_id = "xxxxxxxxxxxxxxxxx"
url = f"https://api.meraki.com/api/v1/networks/{network_id}/appliance/firewall/l3FirewallRules"
headers = {
'X-Cisco-Meraki-API-Key': api_key,
'Content-Type': 'application/json'
}
response = requests.get(url, headers=headers, verify=False)
existingrules = response.json()
print(response.status_code)
print(response.text.encode('utf8'))
with open('ExistingRules.json', 'w') as json_file:
json.dump(response.json(), json_file, indent=4)
newrule = '''{
"rules": [
{
"comment": "Test RuleNEW.",
"policy": "deny",
"protocol": "any",
"destPort": "any",
"destCidr": "192.200.1.0/24",
"srcPort": "Any",
"srcCidr": "Any",
"syslogEnabled": false
}
]
}'''
existingrules.append(newrule)
response = requests.put(url, headers=headers, json=existingrules, verify=False)
As mentioned by @David_Jirku it is probable that appending is not going to behave as you wish.
Usually the last rule in the list is 'allow any-any', if you append a stricter rule it will be ignored as it will be lower in precedence than 'allow any-any'
To add a rule to existingrules, you should be adding an item to the rules array within it, not direct to existingrules.
Have a look at...
https://github.com/CiscoSE/AddMerakiMXL3FirewallRuleToNetworks/blob/master/AddRulesToMXL3Firewall.py
You could do something along the lines of
# Constants
netowrk_id = "xxxx"
TargetComment = "Google DNS"
# Get Rules for network
FirewallRules = dashboard.appliance.getNetworkApplianceFirewallL3FirewallRules(network_id)
# Determine idx where rule should be updated
target_idx = next((idx for idx, item in enumerate(FirewallRules['rules']) if item['comment'] == TargetComment), None)
# Update rule
FirewallRules['rules'][target_idx].update({
"comment": TargetComment
"policy": "deny",
"protocol": "any",
"destPort": "any",
"destCidr": "192.200.1.0/24",
"srcPort": "Any",
"srcCidr": "Any",
"syslogEnabled": false
})
# Remove Default Rule entry
FirewallRules['rules'].pop(-1)
# Update Rules
NewSetOfRules = dashboard.appliance.updateNetworkApplianceFirewallL3FirewallRules(
networkId=network_id,
rules=FirewallRules['rules']
)
What happens is, that I search through all the elements in FirewallRules for the index of the element with the comment, of the rule I wan't to update.
With the index in hand, I can either update the rule, or use the index to inject a new rule. In the above example I use it to update a specific rule.
Modify it to suit your implementation.
thanks. I have a working script now which does a similar thing. It injects the new rule to the existing but the rules are still updated by completely overwriting all existing rules with the list I send in - apparently that's the way it works, rather than just adding in any new items in the index.
It seems to work ok but I found that when you do the GET, the list includes the default allow rule. Then when you go to PUT the updated list - that Allow rule gets added like it was a custom rule so you end up with the default and an extra identical copy.
You get an extra Allow rule every time you do an update. I fixed this by searching the list index for that rule and deleting it (luckily the comment is "default Rule" which is nicely unique) before adding my new rule and sending it back.
Or use the a posteriori knowledge, that the default rule is always at the end of the list and simpy "pop it". 😉
# Remove Default Rule entry
FirewallRules['rules'].pop(-1)