<?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>Firepower on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/firepower/</link>
    <description>Recent content in Firepower 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>Fri, 17 Jul 2020 20:41:09 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/firepower/index.xml" rel="self" type="application/rss+xml" />
    <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>
