<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Firewall on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/firewall/</link>
    <description>Recent content in Firewall on 0x2142 | Networking Nonsense</description>
    <image>
      <title>0x2142 | Networking Nonsense</title>
      <url>https://0x2142.com/logo.jpg</url>
      <link>https://0x2142.com/logo.jpg</link>
    </image>
    <generator>Hugo -- 0.143.1</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 02 Aug 2022 14:50:51 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/firewall/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[How to] Set up Wireguard VPN on OPNsense</title>
      <link>https://0x2142.com/how-to-set-up-wireguard-on-opnsense/</link>
      <pubDate>Tue, 02 Aug 2022 14:50:51 +0000</pubDate>
      <guid>https://0x2142.com/how-to-set-up-wireguard-on-opnsense/</guid>
      <description>In this post, we&amp;rsquo;ll walk through a simple WireGuard remote-access VPN configuration on OPNsense - including client setup examples with Windows &amp;amp; Android.</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/b58PpuIsQ3A?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>So in my <a href="/opnsense-qotom-q750gs/">last post</a>, I picked up a Qotom Mini-PC to run OPNsense on. After a few months, the device has been running well &amp; I&rsquo;m very happy with it.</p>
<p>One of the new things I got to try out with OPNsense was Wireguard VPN. I had previously been using something else for VPN connectivity back to my home network - but I heard good things about Wireguard &amp; wanted to give it a try.</p>
<p>So far my experience has been good! I&rsquo;ve been pleasantly suprised with how easy it is to configure &amp; get running. In addition, the performance &amp; overall experience has been very positive. The VPN connects quicker than anything I&rsquo;ve used in the past, and just simply works without issue.</p>
<p>All that being said - I wanted to put together a quick guide on how to configure Wireguard on OPNsense. Specifically, this configuraion will be for remote-access VPN - where clients will connect to a VPN headend. We&rsquo;ll walk through the OPNsense configuration &amp; a few clients as well. So let&rsquo;s dig in!</p>
<hr>
<h2 id="topology">Topology</h2>
<p>For the purpose of this blog post, we&rsquo;ll be using the lab topology below:</p>
<p><img alt="topology" loading="lazy" src="/content/images/2022/07/topology.png#center"></p>
<p>I will be using the reserved IP range 203.0.113.0/24 for the WAN-side addressing. I&rsquo;ll have one Windows &amp; one Android client that we&rsquo;ll walk through &amp; connect to the VPN.</p>
<h2 id="installing-the-wireguard-plugin">Installing the Wireguard Plugin</h2>
<p>To get started, first thing we will want to do is install the Wireguard plugin for OPNsense. By default, OPNsense will have standard IPSec &amp; OpenVPN already available - but other VPN options can be enabled easily.</p>
<p>So in OPNsense, we&rsquo;ll navigate down to <strong>System &gt; Firmware &gt; Plugins</strong>, then search for <strong>wireguard</strong> and click the plus icon.</p>
<p><img alt="addplugin" loading="lazy" src="/content/images/2022/07/addplugin.png#center"></p>
<p>This should pull down the package &amp; install pretty quickly. No reboot required here!</p>
<p><img alt="plugindownload" loading="lazy" src="/content/images/2022/07/plugindownload.png#center"></p>
<p>Once installed, you may have to refresh the page or navigate to a new page so that the menu bar has a chance to reload. Then we&rsquo;ll have a new option under <strong>VPN</strong>:</p>
<p><img alt="vpnmenu" loading="lazy" src="/content/images/2022/07/vpnmenu.png#center"></p>
<h2 id="wireguard-tunnel-configuration">Wireguard Tunnel Configuration</h2>
<p>Next we&rsquo;ll begin configuring Wireguard on the OPNsense side.</p>
<p>There is a little bit of a chicken &amp; egg scenario here since everything is based on cryptographic keys. We&rsquo;ll need to generate keys on the firewall, which we need to enter on the client - but we also need the client keys to enter on the firewall. A bit of bouncing between the two - but for now we&rsquo;ll try to complete as much as we can on the firewall side.</p>
<p>We&rsquo;ll enable Wireguard by dropping down to <strong>VPN &gt; WireGuard</strong> then clicking <strong>Enable</strong> and <strong>Apply</strong></p>
<p><img alt="enablewg" loading="lazy" src="/content/images/2022/07/enablewg.png#center"></p>
<p>Next we&rsquo;ll set up the Wireguard tunnel interface on OPNsense. This will be a virtual tunnel interface that will be created as interface <code>wg&lt;instance number&gt;</code>.</p>
<p>To do this, we&rsquo;ll navigate to the <strong>Local</strong> tab, and click the plus icon to add a new tunnel.</p>
<p><img alt="localconfig-pt1-1" loading="lazy" src="/content/images/2022/07/localconfig-pt1-1.png#center"></p>
<p>In the above screenshot, I&rsquo;ve filled in just a few details.</p>
<p>For <strong>Name</strong>, I&rsquo;ve entered our virtual interface name <code>wg1</code>. Since OPNsense shows the <strong>Instance</strong> as <code>1</code> - it will create a <code>wg</code> interface with that instance number.</p>
<p>We&rsquo;ll leave <strong>Public Key</strong> &amp; <strong>Private Key</strong> blank for now. OPNsense will auto-generate these keys once we save this config.</p>
<p>For <strong>Listen Port</strong>, I&rsquo;ve set this to 51820 which is the default for Wireguard. It&rsquo;s not stated here, but this is a UDP tunnel.</p>
<p>For <strong>Tunnel Address</strong> - this is where we add the IP address of the virtual tunnel interface. This will be the gateway for our remote clients. In my lab I&rsquo;ll be using 10.50.50.1/24 here.</p>
<p>We have no peers configured yet - so we can&rsquo;t select any here. We&rsquo;ll leave this blank for now, but come back later.</p>
<p>We will leave <strong>Disable Routes</strong> unchecked. By default, OPNsense will add static/connected routes for any client via the tunnel interface. You might not want this behavior if you wanted to do custom routing - for example, in a site-to-site VPN connection - but we&rsquo;ll leave this enabled.</p>
<p>Once we&rsquo;re done, we&rsquo;ll click <strong>Save</strong>.</p>
<p>Like I mentioned before, OPNsense will now auto-generate our crypto keys for the tunnels. So if we edit our tunnel, we&rsquo;ll now see those fields populated:</p>
<p><img alt="localconfig-pt2-1" loading="lazy" src="/content/images/2022/07/localconfig-pt2-1.png#center"></p>
<p>We&rsquo;ll want to copy the <strong>Public Key</strong> &amp; save it for later. This will need to be imported onto our clients, so that they can communicate securely with our firewall.</p>
<h2 id="wireguard-interface-assignment">Wireguard Interface Assignment</h2>
<p>Now that we have our headend tunnel interface defined, we can map our <code>wg1</code> interface to an OPNsense interface. The OPNsense documentation suggests this is optional, but I would recommend it since it will allow us to create firewall rules to permit/deny access to clients.</p>
<p>We&rsquo;ll navigate to <strong>Interfaces &gt; Assignments</strong>, and we should see a <strong>New interface</strong> available: our <code>wg1</code> tunnel.</p>
<p>We can assign this a name, then click the plus icon &amp; <strong>Save</strong>.</p>
<p><img alt="newinterface" loading="lazy" src="/content/images/2022/07/newinterface.png#center"></p>
<p>Next we&rsquo;ll enable the interface by navigating to <strong>Interfaces &gt; WG1</strong>. Here we&rsquo;ll only need to click <strong>Enable</strong> &amp; save the change - nothing else is necessary.</p>
<p><img alt="enableinterface" loading="lazy" src="/content/images/2022/07/enableinterface.png#center"></p>
<p>Of course, we&rsquo;ll be prompted to apply the changes - which we will do:</p>
<p><img alt="applychangeinterface" loading="lazy" src="/content/images/2022/07/applychangeinterface.png#center"></p>
<p>By default, all traffic through our <code>WG1</code> firewall interface will be blocked - so please make sure to configure a firewall rule to permit traffic from the Wireguard clients.</p>
<h2 id="firewall-rules-allow-inbound-wireguard-traffic">Firewall Rules: Allow Inbound Wireguard Traffic</h2>
<p>Next we need to permit the Wireguard traffic into our firewall. By default the WAN interface will block all traffic that isn&rsquo;t explicitly allowed - including our Wireguard traffic.</p>
<p>For this, we&rsquo;ll navigate to <strong>Firewall &gt; Rules &gt; WAN</strong>. Then click the plus icon to add a new rule.</p>
<p><img alt="waninboundfwrule" loading="lazy" src="/content/images/2022/07/waninboundfwrule.png#center"></p>
<p>The screenshot above shows what our firewall rule will look like.</p>
<p>Here&rsquo;s the summary:</p>
<ul>
<li>Action: Pass</li>
<li>Interface: WAN</li>
<li>Direction: In</li>
<li>TCP/IP Version: IPv4
<ul>
<li>You can enable IPv6 as well, if you have IPv6 connectivity (this is a lab box, which does not)</li>
</ul>
</li>
<li>Protocol: UDP</li>
<li>Source: Any
<ul>
<li>This will allow anyone on the internet to reach our VPN. We could restrict source IP addresses, if our clients had permanent, static IPs.</li>
</ul>
</li>
<li>Destination: WAN Address
<ul>
<li>The firewall itself is the destination for this traffic</li>
</ul>
</li>
<li>Destination Port Range: (other) / 51820</li>
</ul>
<p>I also enabled logging &amp; added a quick description. After we have all this configured, we can click <strong>Save</strong> - then <strong>Apply Changes</strong>.</p>
<h2 id="client-setup---windows">Client Setup - Windows</h2>
<p>So first we&rsquo;ll start with an easy configuration on a Windows client. Wireguard client software can be found on the Wireguard site <a href="https://www.wireguard.com/install/">here</a>.</p>
<p>For the sake of the walkthrough, we will manually configure each client. However, this can be a difficult task if there is a large number of clients. Wireguard does support importing configurations, and there are a number of free tools available to help automate generating config files for clients - including some which will generate QR codes for easy import on mobile clients.</p>
<p>So on my lab Windows machine, we&rsquo;ll open up the Wireguard client &amp; click <strong>Add Empty Tunnel:</strong></p>
<p><img alt="windows-addnew" loading="lazy" src="/content/images/2022/07/windows-addnew.png#center"></p>
<p>Then we&rsquo;ll be given a blank config file, with only the devices public &amp; private key pair generated for us.</p>
<p>We&rsquo;re going to fill in details similar to the below screenshot:</p>
<p><img alt="windows-tunnelconfig" loading="lazy" src="/content/images/2022/07/windows-tunnelconfig.png#center"></p>
<p>The configuration under [Interface] is the local, client-side configuration. I&rsquo;ve added the client&rsquo;s tunnel address - which will be 10.50.50.15/32. I&rsquo;ve also configured DNS servers which the client can reach via the VPN.</p>
<p>Then we&rsquo;ll add the [Peer] section, which contains info about our VPN headend. Here&rsquo;s where we&rsquo;ll need the public key from our OPNsense firewall. We&rsquo;ll also specify the <strong>Endpoint</strong> address, which is the IP or hostname of our VPN headend &amp; the port (which by default is 51820).</p>
<p>We&rsquo;ve also configured <strong>AllowedIPs</strong> as <code>0.0.0.0/0</code>. This will force <strong>all</strong> client traffic over the VPN tunnel - including general internet traffic. However, we could limit this to specific subnets. For example, let&rsquo;s say your network only used 172.16.90.0/24 &amp; 10.1.1.0/16 subnets &amp; we only wanted the user to be able to access those. We would configure the following: <code>AllowedIPs = 172.16.90.0/24, 10.1.1.0/16</code>. In this case, only traffic for those subnets would be routed over the VPN - any other traffic would use the devices default internet connection.</p>
<p>Now, we&rsquo;ll save this - and again need to copy the device&rsquo;s <strong>Public Key</strong>, which we&rsquo;ll need to enter on the OPNsense firewall.</p>
<h2 id="client-setup---adding-clients-to-opnsense">Client Setup - Adding Clients to OPNsense</h2>
<p>In order for the Windows machine to connect to OPNsense, we&rsquo;ll also need to configure a client profile on the firewall.</p>
<p>In OPNsense, we&rsquo;ll navigate back to <strong>VPN &gt; WireGuard</strong>, then click on the <strong>Endpoints</strong> tab.</p>
<p><img alt="wg-client-win" loading="lazy" src="/content/images/2022/07/wg-client-win.png#center"></p>
<p>Here we&rsquo;ll configure a name for our client &amp; paste in the client&rsquo;s <strong>Public Key</strong>.</p>
<p>We&rsquo;ll also set <strong>AllowedIPs</strong> to the client&rsquo;s IP address, which we have configured as <code>10.50.50.15/32</code>. This controls what IP addresses are reachable via this endpoint.</p>
<p>We do have some additional fields available, which we will leave blank. For example - <strong>Endpoint Address</strong> &amp; <strong>Endpoint Port</strong> would be used to define a public IP that we expect our client to connect from. Since a remote-access client could connect from any IP, we leave those fields blank to allow this.</p>
<p>Once we&rsquo;re done, we&rsquo;ll click <strong>Save</strong> then <strong>Apply</strong>.</p>
<p>Next we&rsquo;ll jump back to the <strong>Local</strong> tab, and edit our headend tunnel configuration.</p>
<p>We should now see our windows client in the <strong>Peers</strong> dropdown. We&rsquo;ll select that client, so that Wireguard will permit that client to connect via this tunnel interface.</p>
<p><img alt="localconfig-pt3" loading="lazy" src="/content/images/2022/07/localconfig-pt3.png#center"></p>
<p>Then, as always, click <strong>Save</strong> &amp; <strong>Apply</strong></p>
<p>Last but not least - we should restart our Wireguard server on OPNsense. This can be done by either disabling &amp; re-enabling Wireguard - or by navigating back to the OPNsense dashboard &amp; clicking the restart icon next to the <strong>wireguard-go</strong> service.</p>
<h2 id="testing-the-connection">Testing The Connection</h2>
<p>Okay - now that we have all that completed, it&rsquo;s finally time to test connectivity from our client.</p>
<p>On my Windows client, I&rsquo;ll just click the <strong>Activate</strong> button for the tunnel:</p>
<p><img alt="windows-tunnelconnected" loading="lazy" src="/content/images/2022/07/windows-tunnelconnected.png#center"></p>
<p>And we&rsquo;ll see that the <strong>Status</strong> shows <strong>Active</strong>, and we&rsquo;ll start to see updates to the <strong>Last Handshake</strong> &amp; <strong>Transfer</strong> fields - indicating we are connected &amp; sending data.</p>
<p>To further validate, we can check the <strong>Log</strong> tab:</p>
<p><img alt="windows-log" loading="lazy" src="/content/images/2022/07/windows-log.png#center"></p>
<p>The key things to look for here, are the following messages:</p>
<ul>
<li><strong>Receiving handshake response</strong> which indicates our firewall responded to our request to connect</li>
<li><strong>Keypair 1 created</strong> which indicates that our connection is healthy to our peer</li>
<li><strong>Receiving keepalive packet from peer</strong> - we should see these periodically to maintain our connection. If keepalives stop flowing, then we may have a break in connectivity between client &amp; peer.</li>
</ul>
<p>We should also be able to reach out <code>wg1</code> tunnel address from the Windows client:</p>
<p><img alt="windows-ping" loading="lazy" src="/content/images/2022/07/windows-ping.png#center"></p>
<p>On the OPNsense side of things, we can check what client is connected via the <strong>List Configuration</strong> tab, under <strong>VPN &gt; Wireguard</strong>:</p>
<p><img alt="wireguard-listconfig" loading="lazy" src="/content/images/2022/07/wireguard-listconfig.png#center"></p>
<p>On this screen, we can see we have 1 peer connected - which matches the IP &amp; public key of our Windows client. We&rsquo;ll see similar output like the Windows client, where we can see the latest handshake &amp; data transfer.</p>
<p>Additionally, for easier access we can add the Wireguard widget to the OPNsense dashboard.</p>
<p>If we navigate to our dashboard, then click <strong>Add widget</strong> - we can add the <strong>Wireguard</strong> widget.</p>
<p>Here&rsquo;s what that looks like:</p>
<p><img alt="wg-widget" loading="lazy" src="/content/images/2022/07/wg-widget.png#center"></p>
<h2 id="additional-client-setup---android">Additional Client Setup - Android</h2>
<p>Let&rsquo;s also take a quick look at a mobile client. I&rsquo;ll get through this pretty quickly, since the configuration will be very similar to our other client - just a different UI.</p>
<p>On my Android device - if I open up the Wireguard app, I have a few options for creating or importing a tunnel:</p>
<p><img alt="android-create-1" loading="lazy" src="/content/images/2022/07/android-create-1.png#center"></p>
<p>In this case, we&rsquo;ll again walk through creating a tunnel manually. Again, it&rsquo;s worth mentioning that there are 3rd party apps available to auto-generate config imports or QR codes to make this easier.</p>
<p>First we&rsquo;ll have a handful of fields to fill in about the Android-side configuration. This includes a name for the tunnel, an address, and DNS servers.</p>
<p>We can click on the refresh icon in the <strong>Private Key</strong> field to auto-generate our key pairs:</p>
<p><img alt="android-interface-1" loading="lazy" src="/content/images/2022/07/android-interface-1.png#center"></p>
<p>One of the interesting parts of the mobile app is the ability to permit/exclude individual apps from traversing the VPN tunnel.</p>
<p>Here&rsquo;s a quick screenshot, where we can pick either to allow only certain apps to use the VPN - or permit all except a few we choose to exclude:</p>
<p><img alt="android-app-list-1" loading="lazy" src="/content/images/2022/07/android-app-list-1.png#center"></p>
<p>In my case, I&rsquo;ll keep this set to allow all applications.</p>
<p>Next we&rsquo;ll work on the peer config:</p>
<p><img alt="android-peer-1" loading="lazy" src="/content/images/2022/07/android-peer-1.png#center"></p>
<p>After that, all we need to do is save our VPN configuration - then we can toggle the tunnel on or off:</p>
<p><img alt="android-enable-1" loading="lazy" src="/content/images/2022/07/android-enable-1.png#center"></p>
<p>And similar to the Windows client, we can click on the tunnel itself to see current status / data transfer:</p>
<p><img alt="android-stats-1" loading="lazy" src="/content/images/2022/07/android-stats-1.png#center"></p>
<p>So it looks like we should be connected &amp; can try accessing VPN resources!</p>
<p>We can also use the same methods as earlier to check connectivity from the OPNsense side.</p>
<hr>
<p>Okay - that&rsquo;s all I wanted to share today. I&rsquo;ve been quite pleased with how easy to use WireGuard has been  - and how well it performs! I hope this blog post was helpful if you&rsquo;re interested in trying it out.</p>
<p>Thanks!!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cisco Firepower - Automating Cellular Failover</title>
      <link>https://0x2142.com/cisco-firepower-automating-cellular-failover/</link>
      <pubDate>Fri, 17 Jul 2020 20:41:09 +0000</pubDate>
      <guid>https://0x2142.com/cisco-firepower-automating-cellular-failover/</guid>
      <description>How I automated internet uplink monitoring &amp;amp; route injection on a Cisco Firepower Firewall</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Mf5zIt9HFxk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>In a <a href="/how-to-setting-up-google-fi-on-a-netgear-lte-modem/">recent post</a>, I walked through setting up a Netgear LTE cellular modem with Google Fi. Might seem random - but I had a plan for this!</p>
<p>In trying to ensure I keep a stable internet connection for work, I eventually led myself down the path of buying a cellular modem. That was part one. The next step was being able to somewhat-intelligently monitor my home internet connection, and then fail over to the cell modem when connectivity was poor (think lazy SDWAN).</p>
<p>At first I figured this would be easy&hellip;. but of course nothing is :)</p>
<p>I currently have a Cisco FirePower 1010 appliance that I&rsquo;m using for a home firewall. Unfortunately, while this thing does support IP SLA - It wouldn&rsquo;t quite accomplish what I wanted to do. And to be fair, this box is intended to be a <em>security</em> appliance - not SDWAN.</p>
<p>Anyways - I talked myself into just writing some Python automation to monitor my home internet, and dynamically inject/remove static routing entries. I needed a new project anyways</p>
<p><em>For those of you wanting to jump straight to the code, the GitHub repo is <a href="https://github.com/0x2142/fpwr-route-failover">here</a></em></p>
<hr>
<h2 id="part-1---monitoring-packet-loss--latency">Part 1 - Monitoring Packet Loss &amp; Latency</h2>
<p>So first thing - I regularly experience both complete internet outages (always at the worst times), as well as very high latency. Packet loss seems to be less common with my ISP - but hey, it happens sometimes too.</p>
<p>I opted to use the <a href="https://pypi.org/project/pythonping/">pythonping</a> module as an easy way to collect some of the info I needed.</p>
<p>After importing the module, it&rsquo;s as easy as specifying a destination, packet size, and number of ICMP messages to send:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pythonping</span> <span class="kn">import</span> <span class="n">ping</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">ping</span><span class="p">(</span><span class="s1">&#39;8.8.8.8&#39;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>To simplify at least one part of the operation, pythonping has a built in function to return the average round trip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">rtt_avg_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mf">74.23</span>
</span></span></code></pre></div><p>The actual contents of our ping results are going to be in the following format:
<code>Reply from 8.8.8.8, 10 bytes in 43.82ms</code></p>
<p>So a quick way for me to figure out packet loss was to simply search for the &ldquo;Reply from&rdquo; text in each response:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">lost</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">packet</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s2">&#34;Reply from&#34;</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">lost</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span></code></pre></div><p>Easy enough - and we can use that to calculate the amount of packet loss against the 10 total packets sent:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lossperc = 100 * lost / 10
</span></span></code></pre></div><p>Once all that is figured out, we can use a simple expression to determine whether or not the loss or latency thresholds have been exceeded:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">if</span> <span class="n">response</span> <span class="o">&gt;=</span> <span class="n">MAX_LATENCY</span> <span class="ow">or</span> <span class="n">loss</span> <span class="o">&gt;=</span> <span class="n">MAX_LOSS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Loss/Latency violate thresholds.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Loss/Latency within thresholds.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span></code></pre></div><p>The code I wrote then takes one of two paths.</p>
<ul>
<li>If the loss and latency is ABOVE our thresholds, call to the FirePower module to inject a static default route over the LTE modem</li>
<li>If the loss and latency is BELOW our thresholds, call to the FirePower module to delete the static default route - which returns traffic to the primary internet</li>
</ul>
<p>These functions within the FirePower module were written so that each change will only be made once. For example, if the script checks and finds that the loss/latency is bad, but the static route towards the LTE modem already exists - then no additional changes are made.</p>
<p>So next - Let&rsquo;s take a look at the FirePower module.</p>
<h2 id="part-2---authenticating-to-fdm--initial-checks">Part 2 - Authenticating to FDM &amp; Initial Checks</h2>
<p>This was my first time getting into the FirePower FDM APIs - but they ended up being fairly straightforward to use.</p>
<p>Turns out that FDM has built-in API documentation, which is extremely helpful. This can be found at <code>https://&lt;FDM IP&gt;/#/api-explorer</code></p>
<p><img alt="firepower-api-explorer" loading="lazy" src="/content/images/2020/07/firepower-api-explorer.PNG#center"></p>
<p>Okay - So first thing&rsquo;s first. Authenticating to the firewall:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">oauth_data</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;grant_type&#34;</span><span class="p">:</span> <span class="s2">&#34;password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;admin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;Cisco1234!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">baseurl</span> <span class="o">=</span> <span class="s2">&#34;https://&#34;</span> <span class="o">+</span> <span class="n">FDM</span> <span class="o">+</span> <span class="s2">&#34;/api/fdm/latest&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">authurl</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/fdm/token&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Posting AUTH request to FDM&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># POST request to FDM with headers / oauth data</span>
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">s</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">authurl</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                   <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">oauth_data</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                   <span class="n">verify</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># If success - only pull out &amp; return the auth token</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Auth success - got token.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">)[</span><span class="s1">&#39;access_token&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Authentication Failed.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span></code></pre></div><p>In the code above - we take the headers and some basic authentication info (username, password, grant type) and send it in an HTTP POST request to <code>https://&lt;FDM IP&gt;/api/fdm/latest/fdm/token</code></p>
<p>This returns an authentication token, which we&rsquo;ll need to include in any further HTTP request to the firewall. This can be done by sending a new set of headers in the future:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">token</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next we need to figure out which routing table to insert the route into. Since I am only using the default routing table, the script will just grab the UID for the default global routing table:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">vr_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routing_table</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">vr_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span></code></pre></div><p>I mentioned earlier that the script will not make any changes if the firewall is already in the desired state. So we will take time now to check what state the firewall is in, before attempting to make any changes.</p>
<p>This is accomplished by making our first primary action scanning the routing table to see if our route already exists:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">route_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> \
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">route_data</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">route_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Convert returned JSON object</span>
</span></span><span class="line"><span class="cl"><span class="n">current_routes</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">route_data</span><span class="p">)[</span><span class="s1">&#39;items&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Run through each route returned and see if it matches the one we&#39;re looking for</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">route</span> <span class="ow">in</span> <span class="n">current_routes</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># For each route, we need to manually go look up the uid of our gateway &amp; network objects</span>
</span></span><span class="line"><span class="cl">    <span class="n">gateway</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNetworkObject</span><span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">dest_network</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNetworkObject</span><span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">])</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Match based on route prefix &amp; upstream next hop gateway</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">gateway</span> <span class="o">==</span> <span class="n">GATEWAY</span> <span class="ow">and</span> <span class="n">dest_network</span> <span class="o">==</span> <span class="n">ROUTE</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Found route to </span><span class="si">%s</span><span class="s2"> via </span><span class="si">%s</span><span class="s2">&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">dest_network</span><span class="p">,</span> <span class="n">gateway</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">route</span>
</span></span></code></pre></div><p>Depending on whether we were looking to add or remove a route, the script may proceed with doing so - or just quit if the action has already been taken previously.</p>
<h2 id="part-3---failover-add-static-route">Part 3 - Failover (Add Static Route)</h2>
<p>Assuming that we need to <strong>Add</strong> a route, we need to sent a POST request to <code>https://&lt;FDM IP&gt;/api/fdm/latest/devices/default/routing/virtualrouters/&lt;Virtual Router ID&gt;/staticrouteentries</code> with some required information (like subnet info, next-hop, etc)</p>
<p>Unfortunately, we can&rsquo;t just send a POST with the target subnet &amp; gateway. Those need to be created as network objects on the FirePower box beforehand. For each network object, we need to build a quick JSON object with the required info:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">host_data</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;Created by ISP Failover automation&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;subType&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">subtype</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;value&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">address</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span></code></pre></div><p>Then we can send a POST to the FDM API to create the object with our supplied parameters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">posturl</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/object/networks&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">posturl</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">host_data</span><span class="p">)</span>
</span></span></code></pre></div><p>Once we have those objects created, we can compile all of that info into a JSON object with all the components needed to create a static route entry:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">routeobject</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;route_BACKUP&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;Created by ISP Failover automation&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">iface_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;physicalinterface&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">iface_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[{}]</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">network_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">network_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">gateway_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">gateway_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;metricValue&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;ipType&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;IPv4&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;staticrouteentry&#34;</span>
</span></span></code></pre></div><p>Then pass all that into a POST request to create the route object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">add_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">add_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">routeobject</span><span class="p">)</span>
</span></span></code></pre></div><p>Success? Well not quite yet.</p>
<p>FirePower requires all changes to be deployed (or applied) to the system before they take effect.</p>
<p>So next we need to initiate a deployment task - and periodically check on it. Deployments can take a <em>while</em> to complete, depending on number of changes, processing power of the appliance, etc.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># First send a POST to the deployment endpoint:</span>
</span></span><span class="line"><span class="cl"><span class="n">deploy_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/operational/deploy&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">deploy_response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">deploy_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Grab the deployment task UID:</span>
</span></span><span class="line"><span class="cl"><span class="n">deploymentID</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">deploy_response</span><span class="o">.</span><span class="n">text</span><span class="p">)[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Loop while deployment is running:</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="n">deployed</span> <span class="ow">is</span> <span class="kc">False</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Sleep for a few seconds:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Get list of all deployment tasks:</span>
</span></span><span class="line"><span class="cl">    <span class="n">tasklist</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">deploy_url</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">taskList</span><span class="p">[</span><span class="s1">&#39;items&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">deploymentID</span> <span class="ow">and</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;DEPLOYED&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deployment status is: &#34;</span> <span class="o">+</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">deployed</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">deploymentID</span> <span class="ow">and</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;DEPLOYED&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># If changes not yet deployed, check again momentarily</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deployment status is: &#34;</span> <span class="o">+</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">deployed</span> <span class="o">=</span> <span class="kc">False</span>
</span></span></code></pre></div><p>So in the above block, we POST a request to deploy changes. The response of that POST request contains our deployment ID, which we keep to check the status later.</p>
<p>Then the script waits a few seconds, and pulls a list of all deployment tasks. Search through that list for our deployment ID - and check to see if it has been completed yet. If not, wait and run the loop again.</p>
<p>And that&rsquo;s it! Once we get confirmation that our changes have been deployed - we&rsquo;ve now routed traffic over the secondary connection (an LTE modem, in my case).</p>
<h2 id="part-4---fail-back-delete-static-route">Part 4 - Fail-Back (Delete Static Route)</h2>
<p>Okay - this section will be fairly quick. We&rsquo;ve already looked at authenticating, checking if our route already exists, and how to add a new route. So the only thing remaining is removing our route if we wanted to migrate traffic back to the primary connection.</p>
<p>Same logic applies as earlier. If our path monitoring script runs and finds that loss &amp; latency are within the thresholds we set, then we make a call to the firepower module. First we check to ensure there <em>is</em> a route for us to delete, and if so - proceed with deleting it.</p>
<p>Adding a route is a little more work, since we may need to create network objects. However, when we delete the route - we&rsquo;ll just leave those objects on the FirePower box. They&rsquo;ll be there for the next time we need them (which also speeds up deployment times).</p>
<p>To get started, we just need the UID for the route we want to delete.</p>
<p>Remember that code I showed above, where we check to see if the static route exists? And match on our intended subnet / gateway pair? Well, I just made that into a function that will return the UID of the route if it finds it. Easy enough!</p>
<p>Next, we just send an HTTP DELETE to the static routing endpoint - and reference that UID:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">del_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> \
</span></span><span class="line"><span class="cl">          <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries/&#34;</span> <span class="o">+</span> <span class="n">route</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">del_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span></code></pre></div><p>Once that succeeds - we use the same logic as earlier to deploy the changes.</p>
<p>After that, our route is removed &amp; traffic should be flowing over our primary connection again.</p>
<hr>
<p>If it helps anyone - I also threw together a quick diagram to explain the flow of operations here:
<img alt="flow-diagram" loading="lazy" src="/content/images/2020/07/flow-diagram.jpg#center"></p>
<hr>
<p>Well - that&rsquo;s it. This was a fun side project to work on over the past week or two. I hadn&rsquo;t spent much time with the FirePower/FDM APIs just yet. While there was a bit of work in creating all the necessary network objects, the overall process was fairly simple.</p>
<p>It helped tremendously that the FDM API explorer exists, and is available on-box. This utility allows you to see all the available API calls, what parameters they require, and even run test calls from the web UI. This greatly reduced the time needed to figure this out.</p>
<p>Hope this was interesting. If you would like to see the whole project - check out the repo on my GitHub page.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
