Hello im new to the meraki API but i am trying to export all firewall rules to a csv. The code is written in python and successfully exports both L3 and L7 data but for some reason port forwarding comes back as 404. I have full admin privileges with my API key and i know the key is working because it is able to pull L3 and L7. If i go directly to the url https://api.meraki.com/api/v1/networks/{network_id}/appliance/firewall/portForwardingRules on a web browser, i can see the data it should be exporting so i know its valid. This result is consistent across multiple networks. Im not sure where else to trouble . Here is the code in python and results. Thanks.
import requests
import os
import csv
# Constants
API_KEY = 'redacted'
NETWORK_ID = 'redacted'
BASE_URL = 'https://api.meraki.com/api/v1'
OUTPUT_CSV_FILE = 'export_all_firewall_rules.csv'
# Headers for authentication
headers = {
'X-Cisco-Meraki-API-Key': API_KEY,
'Content-Type': 'application/json'
}
# Function to get the Layer 3 firewall rules for the network
def get_l3_firewall_rules(network_id):
url = f'{BASE_URL}/networks/{network_id}/appliance/firewall/l3FirewallRules'
response = requests.get(url, headers=headers)
print(f"Status Code (L3): {response.status_code}") # Debugging line
print(f"Response Body (L3): {response.text}") # Debugging line
if response.status_code == 200:
return response.json().get('rules', [])
else:
print(f"Error retrieving Layer 3 rules: {response.status_code} - {response.text}")
return []
# Function to get the Layer 7 firewall rules for the network
def get_l7_firewall_rules(network_id):
url = f'{BASE_URL}/networks/{network_id}/appliance/firewall/l7FirewallRules'
response = requests.get(url, headers=headers)
print(f"Status Code (L7): {response.status_code}") # Debugging line
print(f"Response Body (L7): {response.text}") # Debugging line
if response.status_code == 200:
return response.json().get('rules', [])
else:
print(f"Error retrieving Layer 7 rules: {response.status_code} - {response.text}")
return []
# Function to get the port forwarding rules for the network
def get_port_forwarding_rules(network_id):
url = f'{BASE_URL}/networks/{network_id}/appliance/portForwardingRules'
response = requests.get(url, headers=headers)
print(f"Status Code (Port Forwarding): {response.status_code}") # Debugging line
print(f"Response Body (Port Forwarding): {response.text}") # Debugging line
if response.status_code == 200:
return response.json()
else:
print(f"Error retrieving port forwarding rules: {response.status_code} - {response.text}")
return []
# Function to export firewall rules to CSV
def export_to_csv(l3_rules, l7_rules, pf_rules, filename):
if not l3_rules and not l7_rules and not pf_rules:
print("No rules to export.")
return
# Open the file in write mode
with open(filename, mode='w', newline='') as file:
fieldnames = [
'Rule Type', 'Policy', 'Protocol', 'Src Port', 'Dst Port', 'Src CIDR', 'Dst CIDR', 'Comment', 'Private IP', 'Public Port'
]
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader() # Write the header row
# Write the Layer 3 firewall rules to the CSV file
for rule in l3_rules:
print(rule) # Debugging line to inspect each rule
if isinstance(rule, dict):
writer.writerow({
'Rule Type': 'Layer 3 Firewall',
'Policy': rule.get('policy', 'N/A'),
'Protocol': rule.get('protocol', 'N/A'),
'Src Port': rule.get('srcPort', 'N/A'),
'Dst Port': rule.get('destPort', 'N/A'),
'Src CIDR': rule.get('srcCidr', 'N/A'),
'Dst CIDR': rule.get('destCidr', 'N/A'),
'Comment': rule.get('comment', 'N/A'),
'Private IP': 'N/A',
'Public Port': 'N/A'
})
# Write the Layer 7 firewall rules to the CSV file
for rule in l7_rules:
print(rule) # Debugging line to inspect each rule
if isinstance(rule, dict):
writer.writerow({
'Rule Type': 'Layer 7 Firewall',
'Policy': rule.get('policy', 'N/A'),
'Protocol': rule.get('protocol', 'N/A'),
'Src Port': 'N/A',
'Dst Port': rule.get('destPort', 'N/A'),
'Src CIDR': 'N/A',
'Dst CIDR': 'N/A',
'Comment': rule.get('comment', 'N/A'),
'Private IP': 'N/A',
'Public Port': 'N/A'
})
# Write the port forwarding rules to the CSV file
for rule in pf_rules:
print(rule) # Debugging line to inspect each rule
if isinstance(rule, dict):
writer.writerow({
'Rule Type': 'Port Forwarding',
'Policy': 'Allow', # Port forwarding rules are typically "Allow"
'Protocol': rule.get('protocol', 'N/A'),
'Src Port': 'N/A',
'Dst Port': rule.get('privatePort', 'N/A'),
'Src CIDR': 'N/A',
'Dst CIDR': 'N/A',
'Comment': rule.get('name', 'N/A'),
'Private IP': rule.get('localIp', 'N/A'),
'Public Port': rule.get('publicPort', 'N/A')
})
print(f"Rules exported to {filename}")
# Main execution
l3_rules = get_l3_firewall_rules(NETWORK_ID)
l7_rules = get_l7_firewall_rules(NETWORK_ID)
pf_rules = get_port_forwarding_rules(NETWORK_ID)
if not l3_rules and not l7_rules and not pf_rules:
print("No firewall or port forwarding rules found.")
else:
export_to_csv(l3_rules, l7_rules, pf_rules, OUTPUT_CSV_FILE)
Status Code (L3): 200
Response Body (L3): {"rules":[{"comment":"test api","policy":"deny","protocol":"tcp","srcPort":"Any","srcCidr":"192.168.128.0/24","destPort":"Any","destCidr":"192.168.127.0/24","syslogEnabled":false},{"comment":"Default rule","policy":"allow","protocol":"Any","srcPort":"Any","srcCidr":"Any","destPort":"Any","destCidr":"Any","syslogEnabled":false}]}
Status Code (L7): 200
Response Body (L7): {"rules":[]}
Status Code (Port Forwarding): 404
Response Body (Port Forwarding): <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Page not found - Cisco Meraki</title>
<style type="text/css">
body {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin: 0 2em;
font-family: Helvetica, Arial, sans-serif;
line-height: 1.45;
color: #222325; /* same as $gray-10 in mkiColorVariables.json */
}
.header__container,
.content__container,
.footer__container {
max-width: 800px;
width: 100%;
}
.header__container {
padding: 2em 0;
border-bottom: 1px solid #D7D7D9; /* same as $gray-80 in mkiColorVariables.json */
}
.header__merakiLogo {
height: 2.125em;
}
.header__merakiLogo--gray {
fill: #898b8e;
}
.header__merakiLogo--green {
fill: #67b346;
}
h1.content__title {
font-size: 1.728em; /* from typographyBase.scss */
font-weight: 300;
margin-bottom: 1em;
}
.content__container {
margin-bottom: 1em;
}
.content__container a {
color: #1D770B; /* same as $linkColor in variables.scss */
}
.footer__container {
padding: 1em 0;
text-align: right;
border-top: 1px solid #D7D7D9; /* same as $gray-80 in mkiColorVariables.json */
}
.footer__text {
font-size: 0.833em;
color: #898A8C; /* same as $gray-50 in mkiColorVariables.json */
}
</style>
</head>
<body>
<div class="header__container">
<svg class="header__merakiLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130 25"><title>Cisco Meraki</title><rect class="header__merakiLogo--gray" x="13.29" y="16.46" width="2.06" height="8.17"/><path class="header__merakiLogo--gray" d="M32,18.8a3.64,3.64,0,0,0-1.73-.44,2.18,2.18,0,1,0,0,4.36A3.63,3.63,0,0,0,32,22.29v2.19a6.53,6.53,0,0,1-1.88.29,4.23,4.23,0,1,1,0-8.46,6.29,6.29,0,0,1,1.88.29Z"/><path class="header__merakiLogo--gray" d="M10.46,18.8a3.59,3.59,0,0,0-1.73-.44,2.18,2.18,0,1,0,0,4.36,3.59,3.59,0,0,0,1.73-.43v2.19a6.49,6.49,0,0,1-1.88.29,4.23,4.23,0,1,1,0-8.46,6.23,6.23,0,0,1,1.88.29Z"/><path class="header__merakiLogo--gray" d="M38.45,18.4a2.15,2.15,0,1,0,2.13,2.15,2.11,2.11,0,0,0-2.13-2.15m4.31,2.15a4.31,4.31,0,1,1-4.31-4.23,4.2,4.2,0,0,1,4.31,4.23"/><path class="header__merakiLogo--gray" d="M23.17,18.32a7.05,7.05,0,0,0-1.61-.25c-.83,0-1.27.27-1.27.67s.6.67.94.78l.57.18A2.46,2.46,0,0,1,23.74,22c0,2.05-1.81,2.74-3.38,2.74a12.56,12.56,0,0,1-2.22-.22V22.67a7.73,7.73,0,0,0,1.95.31c1,0,1.5-.3,1.5-.77s-.41-.65-.92-.81l-.44-.14c-1.15-.36-2.1-1-2.1-2.39,0-1.53,1.14-2.55,3-2.55a8.91,8.91,0,0,1,2,.26Z"/><path class="header__merakiLogo--gray" d="M2,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M7.66,4.87a1,1,0,0,0-2,0v5a1,1,0,0,0,2,0Z"/><path class="header__merakiLogo--gray" d="M13.28,1a1,1,0,0,0-2,0V11.87a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M18.9,4.87a1,1,0,0,0-2,0v5a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M24.52,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M30.14,4.87a1,1,0,0,0-2,0v5a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M35.77,1a1,1,0,1,0-2,0V11.87a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M41.39,4.87a1,1,0,0,0-2.06,0v5a1,1,0,0,0,2.06,0Z"/><path class="header__merakiLogo--gray" d="M47,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--green" d="M68.43,24.64,62.06,8.9V24.64H60.73V7.13h2l6.44,16,6.08-16h1.95V24.64H75.9V9l-6,15.61Z"/><path class="header__merakiLogo--green" d="M91.28,21.18A5.11,5.11,0,0,1,86,25c-3.43,0-6-2.36-6-6.69s2.5-6.69,5.76-6.69,5.71,2.19,5.71,7H81.36c.12,3.6,2.16,5.25,4.69,5.25a3.77,3.77,0,0,0,3.84-2.7Zm-9.9-3.65h8.75c-.17-3.24-2-4.84-4.35-4.84S81.63,14.37,81.38,17.54Z"/><path class="header__merakiLogo--green" d="M94.57,24.64V12h1.07l.19,2.43a3.67,3.67,0,0,1,3.55-2.8,4.51,4.51,0,0,1,1.31.2v1.29a4.93,4.93,0,0,0-1.39-.22c-1.85,0-3.43,1.8-3.43,4.84v6.91Z"/><path class="header__merakiLogo--green" d="M102.35,15.28c.19-2.16,2.12-3.65,4.91-3.65S112,13.21,112,15.79v8.85h-1.07l-.19-2.09A5,5,0,0,1,106.22,25c-2.46,0-4.23-1.33-4.23-3.57s1.31-3.48,4.86-4.06l3.79-.63v-1.1c0-1.82-1.29-2.94-3.45-2.94s-3.31,1-3.48,2.58Zm8.29,4.86V17.78l-3.4.58c-3,.51-3.89,1.46-3.89,3,0,1.7,1.31,2.55,3.21,2.55A4,4,0,0,0,110.64,20.14Z"/><path class="header__merakiLogo--green" d="M115.32,24.64V7.13h1.31v10L122.69,12h1.78L118,17.51l7.3,7.12h-1.77L116.63,18v6.69Z"/><path class="header__merakiLogo--green" d="M126.92,8.93V7.13h1.58v1.8ZM127,24.64V12h1.31V24.64Z"/></svg>
</div>
<div class="content__container">
<h1 class="content__title">Page not found</h1>
<p class="content__primaryText">The page you are looking for may have been moved or does not exist.</p>
<p class="content__secondaryText">To log in to the Cisco Meraki Dashboard, go to <a href="https://dashboard.meraki.com">https://dashboard.meraki.com</a>.</p>
</div>
<div class="footer__container">
<span id="footer" class="footer__text">© Cisco Systems, Inc.</span>
</div>
<script type="text/javascript" charset="utf-8">
//<![CDATA[
(function () {
var year = new Date().getFullYear();
document.getElementById("footer").innerHTML = '© ' + year + ' Cisco Systems, Inc.';
})()
//]]>
</script>
</body>
</html>
Error retrieving port forwarding rules: 404 - <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Page not found - Cisco Meraki</title>
<style type="text/css">
body {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin: 0 2em;
font-family: Helvetica, Arial, sans-serif;
line-height: 1.45;
color: #222325; /* same as $gray-10 in mkiColorVariables.json */
}
.header__container,
.content__container,
.footer__container {
max-width: 800px;
width: 100%;
}
.header__container {
padding: 2em 0;
border-bottom: 1px solid #D7D7D9; /* same as $gray-80 in mkiColorVariables.json */
}
.header__merakiLogo {
height: 2.125em;
}
.header__merakiLogo--gray {
fill: #898b8e;
}
.header__merakiLogo--green {
fill: #67b346;
}
h1.content__title {
font-size: 1.728em; /* from typographyBase.scss */
font-weight: 300;
margin-bottom: 1em;
}
.content__container {
margin-bottom: 1em;
}
.content__container a {
color: #1D770B; /* same as $linkColor in variables.scss */
}
.footer__container {
padding: 1em 0;
text-align: right;
border-top: 1px solid #D7D7D9; /* same as $gray-80 in mkiColorVariables.json */
}
.footer__text {
font-size: 0.833em;
color: #898A8C; /* same as $gray-50 in mkiColorVariables.json */
}
</style>
</head>
<body>
<div class="header__container">
<svg class="header__merakiLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130 25"><title>Cisco Meraki</title><rect class="header__merakiLogo--gray" x="13.29" y="16.46" width="2.06" height="8.17"/><path class="header__merakiLogo--gray" d="M32,18.8a3.64,3.64,0,0,0-1.73-.44,2.18,2.18,0,1,0,0,4.36A3.63,3.63,0,0,0,32,22.29v2.19a6.53,6.53,0,0,1-1.88.29,4.23,4.23,0,1,1,0-8.46,6.29,6.29,0,0,1,1.88.29Z"/><path class="header__merakiLogo--gray" d="M10.46,18.8a3.59,3.59,0,0,0-1.73-.44,2.18,2.18,0,1,0,0,4.36,3.59,3.59,0,0,0,1.73-.43v2.19a6.49,6.49,0,0,1-1.88.29,4.23,4.23,0,1,1,0-8.46,6.23,6.23,0,0,1,1.88.29Z"/><path class="header__merakiLogo--gray" d="M38.45,18.4a2.15,2.15,0,1,0,2.13,2.15,2.11,2.11,0,0,0-2.13-2.15m4.31,2.15a4.31,4.31,0,1,1-4.31-4.23,4.2,4.2,0,0,1,4.31,4.23"/><path class="header__merakiLogo--gray" d="M23.17,18.32a7.05,7.05,0,0,0-1.61-.25c-.83,0-1.27.27-1.27.67s.6.67.94.78l.57.18A2.46,2.46,0,0,1,23.74,22c0,2.05-1.81,2.74-3.38,2.74a12.56,12.56,0,0,1-2.22-.22V22.67a7.73,7.73,0,0,0,1.95.31c1,0,1.5-.3,1.5-.77s-.41-.65-.92-.81l-.44-.14c-1.15-.36-2.1-1-2.1-2.39,0-1.53,1.14-2.55,3-2.55a8.91,8.91,0,0,1,2,.26Z"/><path class="header__merakiLogo--gray" d="M2,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M7.66,4.87a1,1,0,0,0-2,0v5a1,1,0,0,0,2,0Z"/><path class="header__merakiLogo--gray" d="M13.28,1a1,1,0,0,0-2,0V11.87a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M18.9,4.87a1,1,0,0,0-2,0v5a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M24.52,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M30.14,4.87a1,1,0,0,0-2,0v5a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M35.77,1a1,1,0,1,0-2,0V11.87a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--gray" d="M41.39,4.87a1,1,0,0,0-2.06,0v5a1,1,0,0,0,2.06,0Z"/><path class="header__merakiLogo--gray" d="M47,7.68a1,1,0,0,0-2,0V9.83a1,1,0,1,0,2,0Z"/><path class="header__merakiLogo--green" d="M68.43,24.64,62.06,8.9V24.64H60.73V7.13h2l6.44,16,6.08-16h1.95V24.64H75.9V9l-6,15.61Z"/><path class="header__merakiLogo--green" d="M91.28,21.18A5.11,5.11,0,0,1,86,25c-3.43,0-6-2.36-6-6.69s2.5-6.69,5.76-6.69,5.71,2.19,5.71,7H81.36c.12,3.6,2.16,5.25,4.69,5.25a3.77,3.77,0,0,0,3.84-2.7Zm-9.9-3.65h8.75c-.17-3.24-2-4.84-4.35-4.84S81.63,14.37,81.38,17.54Z"/><path class="header__merakiLogo--green" d="M94.57,24.64V12h1.07l.19,2.43a3.67,3.67,0,0,1,3.55-2.8,4.51,4.51,0,0,1,1.31.2v1.29a4.93,4.93,0,0,0-1.39-.22c-1.85,0-3.43,1.8-3.43,4.84v6.91Z"/><path class="header__merakiLogo--green" d="M102.35,15.28c.19-2.16,2.12-3.65,4.91-3.65S112,13.21,112,15.79v8.85h-1.07l-.19-2.09A5,5,0,0,1,106.22,25c-2.46,0-4.23-1.33-4.23-3.57s1.31-3.48,4.86-4.06l3.79-.63v-1.1c0-1.82-1.29-2.94-3.45-2.94s-3.31,1-3.48,2.58Zm8.29,4.86V17.78l-3.4.58c-3,.51-3.89,1.46-3.89,3,0,1.7,1.31,2.55,3.21,2.55A4,4,0,0,0,110.64,20.14Z"/><path class="header__merakiLogo--green" d="M115.32,24.64V7.13h1.31v10L122.69,12h1.78L118,17.51l7.3,7.12h-1.77L116.63,18v6.69Z"/><path class="header__merakiLogo--green" d="M126.92,8.93V7.13h1.58v1.8ZM127,24.64V12h1.31V24.64Z"/></svg>
</div>
<div class="content__container">
<h1 class="content__title">Page not found</h1>
<p class="content__primaryText">The page you are looking for may have been moved or does not exist.</p>
<p class="content__secondaryText">To log in to the Cisco Meraki Dashboard, go to <a href="https://dashboard.meraki.com">https://dashboard.meraki.com</a>.</p>
</div>
<div class="footer__container">
<span id="footer" class="footer__text">© Cisco Systems, Inc.</span>
</div>
<script type="text/javascript" charset="utf-8">
//<![CDATA[
(function () {
var year = new Date().getFullYear();
document.getElementById("footer").innerHTML = '© ' + year + ' Cisco Systems, Inc.';
})()
//]]>
</script>
</body>
</html>
{'comment': 'test api', 'policy': 'deny', 'protocol': 'tcp', 'srcPort': 'Any', 'srcCidr': '192.168.128.0/24', 'destPort': 'Any', 'destCidr': '192.168.127.0/24', 'syslogEnabled': False}
{'comment': 'Default rule', 'policy': 'allow', 'protocol': 'Any', 'srcPort': 'Any', 'srcCidr': 'Any', 'destPort': 'Any', 'destCidr': 'Any', 'syslogEnabled': False}
Rules exported to export_all_firewall_rules.csv