<?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>Junos on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/junos/</link>
    <description>Recent content in Junos 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>Mon, 22 Mar 2021 11:46:00 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/junos/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>JNCIA-Cloud Study Resources (All Free!)</title>
      <link>https://0x2142.com/jncia-cloud-study-resources-all-free/</link>
      <pubDate>Mon, 22 Mar 2021 11:46:00 +0000</pubDate>
      <guid>https://0x2142.com/jncia-cloud-study-resources-all-free/</guid>
      <description>Let&amp;rsquo;s look at some free resources for studying for the JNCIA-Cloud!</description>
      <content:encoded><![CDATA[<p><sup>The post below was contributed by guest author: <a href="https://twitter.com/NikkiMegaplaza">Nicole Henry</a></sup></p>
<hr>
<p>Hey everyone. I&rsquo;ve got another present for you - Free study resources to use while studying for Juniper Network&rsquo;s JNCIA-Cloud cert (As a reminder, I also gave a list of <a href="/jncia-junos-study-resources/">Free study resources for the JNCIA-Junos exam</a>).</p>
<h2 id="1-juniper-open-learning">1) Juniper Open Learning</h2>
<p>If you were familiar with Junos Genius, it was discontinued, but all the Associate level material can be now found <a href="https://learningportal.juniper.net/juniper/user_activity_info.aspx?id=11478&amp;ref=0x2142.com">Juniper Open Learning</a> for free. Lots of videos &amp; practice exams to get your prepared for the Associate level exams!!</p>
<h2 id="2-juniper-networks-day-one-books">2) Juniper Networks Day One Books</h2>
<p>Description: &ldquo;Day One Books cover networking technologies using step-by-step instructions and practical examples written by working engineers.&rdquo; Here&rsquo;s the link to the <a href="https://www.juniper.net/documentation/jnbooks/en_US/day-one-books/?ref=0x2142.com">Day One Books</a>; there are numerous Day One Books, and I guarantee you&rsquo;ll learn a lot. Me, personally, I like to read with physical books, so I bought the &ldquo;Day One: Data Center Fundamentals&rdquo; book for myself. But here&rsquo;s the <a href="https://www.juniper.net/documentation/en_US/day-one-books/DC_Fundamentals.pdf?ref=0x2142.com">pdf version</a> !!!!</p>
<h2 id="3-documentation-aka-techlibrary">3) Documentation (aka &lsquo;TechLibrary&rsquo;)</h2>
<p>First: For any cert, print &amp; follow the <a href="https://www.juniper.net/us/en/training/certification/certification-tracks/cloud-track/?tab=jncia-cloud&amp;ref=0x2142.com">Exam Objectives</a>.</p>
<p>Second: As you&rsquo;ll see from the Exam Objectives, there are a lot of Juniper solutions on this exam. Luckily their &lsquo;TechLibrary&rsquo; is FILLED with all the info you need about the specific products. I really hope you like to read; you&rsquo;ll be doing a lot of that. Tehehehee!</p>
<h2 id="4-network-fun-times-blog">4) Network Fun Times blog</h2>
<p>This guy <a href="https://twitter.com/NetworkFunTimes?ref=0x2142.com">Chris</a> (who is a Juniper Ambassador!!) made a LENGTHY blog post about what he used to study &amp; <a href="https://www.networkfuntimes.com/jncia-cloud-the-ultimate-resource-for-junipers-sdn-certification/?ref=0x2142.com">pass the JNCIA-Cloud</a>. I visited this blog post countless times b/c it is just THAT GOOD. Use it. Bookmark it.</p>
<p>And that&rsquo;s it! Those are all the resources I used to pass the JNCIA-CLOUD. All FREE! I hope you found this thread useful.</p>
<ul>
<li>Nicole</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>JNCIA-Junos Study Resources (All Free!)</title>
      <link>https://0x2142.com/jncia-junos-study-resources/</link>
      <pubDate>Wed, 03 Feb 2021 10:12:03 +0000</pubDate>
      <guid>https://0x2142.com/jncia-junos-study-resources/</guid>
      <description>Thinking about going for the JNCIA-Junos? Here&amp;rsquo;s some great free resources to get you started!</description>
      <content:encoded><![CDATA[<p><sup>The post below was contributed by guest author: <a href="https://twitter.com/NikkiMegaplaza">Nicole Henry</a></sup></p>
<hr>
<p>Hi, hello. Nicole here.</p>
<p>Do you want to diversify your skillset &amp; learn JunOS? Do you want to be able to add JNCIA-Junos to your resume? Here&rsquo;s a thread of the <del>free</del> resources I used!</p>
<p><a href="https://twitter.com/NikkiMegaplaza/status/1351695140310675457">Link to the original twitter thread</a></p>
<h2 id="1-junos-genius">1) Junos Genius</h2>
<p><a href="https://learningportal.juniper.net/juniper/default.aspx">Junos Genius</a> is an amazing, stupendous, fantastic resource. USE THIS!! Create an account, scroll down to Juniper Open Learning, &amp; select JNCIA-Junos (or whichever cert track in which you&rsquo;re interested). At the time of this post, once you finish all the videos &amp; practice test(s), there will be a voucher test. If you pass the voucher test, you&rsquo;ll receive a voucher for 75% off any Associate Level exam. Again, USE JUNOS GENIUS!! Watch all the vids, take all the practice tests.</p>
<p><strong>UPDATE</strong>
All the material from Junos Genius has been moved to <a href="https://learningportal.juniper.net/juniper/default.aspx">Juniper Open Learning</a> within the Learning Portal. The content is the same, just in a new location.</p>
<h2 id="2-day-one-beginners-guide-to-learning-junos">2) &ldquo;Day One: Beginner&rsquo;s Guide to Learning Junos&rdquo;</h2>
<p>It&rsquo;s easy to read and is a very nice complement to Junos Genius. I HIGHLY recommend it. <a href="https://www.juniper.net/documentation/en_US/day-one-books/junos-beginners-guide.pdf">Here&rsquo;s the pdf</a>.</p>
<h2 id="-3-junos-for-ios-engineers">## 3) &ldquo;Junos for IOS Engineers&rdquo;</h2>
<p>If you&rsquo;re familiar with Cisco IOS, &ldquo;<a href="https://www.juniper.net/documentation/jnbooks/en_US/day-one-books/">Junos for IOS Engineers</a>&rdquo; is another option for you. (click the link &amp; scroll down until you see the book). There are a bunch of Day One books, I&rsquo;ve already read 2, so much information!!!</p>
<blockquote>
<p>Side note: Juniper Networks has a collection of books written by industry professionals on a variety of topics. These books are called <a href="https://www.juniper.net/documentation/jnbooks/en_US/day-one-books/">Day One books</a>. They can be found here. And if you&rsquo;re like me and you like physical books, you have the option to buy books from the virtual <a href="https://store.vervante.com/c/v/category_order.html?base_cat=Juniper%20Networks%3aShop%20Day%20One%20Books&amp;pard=juniper">bookstore</a> !!</p></blockquote>
<h2 id="4-juniper-vlabs">4) Juniper vLabs</h2>
<p>Don&rsquo;t have any physical equipment?? No problem!! Use <a href="https://jlabs.juniper.net/vlabs/">vLabs</a> for practice!!! Definitely watch the video on the home page before you get started; it&rsquo;s a good introduction to vlabs &amp; how to use it. Super highly recommend vLabs.</p>
<h2 id="5-youtube">5) Youtube</h2>
<p>Here&rsquo;s some random youtube videos that I really liked:</p>
<ul>
<li>&ldquo;<a href="https://www.youtube.com/watch?v=n_SdUg1JERY&amp;ref=0x2142.com">Using Juniper for the First Time | JunOS CLI</a>&rdquo;</li>
<li>&ldquo;<a href="https://www.youtube.com/watch?v=d6JalryPoNc&amp;ref=0x2142.com">Interface Naming Conventions</a>&rdquo;</li>
<li>&ldquo;<a href="https://www.youtube.com/watch?v=VNLPnFEzcco&amp;ref=0x2142.com">Juniper Device Interfaces</a>&rdquo;</li>
<li>&ldquo;<a href="https://www.youtube.com/watch?v=EmHYaQxft94&amp;ref=0x2142.com">Juniper Networks JNCIA-Junos Certification Practice Test</a>&rdquo;</li>
</ul>
<p>And that&rsquo;s all! Those are the resources I used - all free. I knew nothing about Junos, and it took about 2 months to study for the exam using those resources and I passed on the first attempt. I hope you found this thread useful.</p>
<p>(Shoutout to Matt for letting me use his site to make this post)</p>
]]></content:encoded>
    </item>
    <item>
      <title>Juniper SRX - Automated Route Monitoring</title>
      <link>https://0x2142.com/juniper-srx-automated-route-monitoring/</link>
      <pubDate>Tue, 13 Mar 2018 11:00:20 +0000</pubDate>
      <guid>https://0x2142.com/juniper-srx-automated-route-monitoring/</guid>
      <description>How I automated BGP routing table monitoring for Juniper SRX firewalls</description>
      <content:encoded><![CDATA[<p><sup><em>Note: I may receive commissions for purchases made through links in this post. This is to help support my blog and does not have any impact on my recommendations.</em></sup></p>
<hr>
<p>I have always said that I&rsquo;m not sure I could write code for a living, but I do really enjoy writing scripts that make my life easier. Today&rsquo;s post is a great example of that. The ease of use offered by the Juniper SRX firewalls and JunOS is something that I wish I had in all of my networking infrastructure. Even better, a brand new SRX 300 can be purchased on <a href="https://www.amazon.com/gp/product/B01ICEO2U4/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01ICEO2U4&amp;linkId=35fbe8300af4e5d1e26e7a860782b3ca">Amazon</a> for less than $300 - which made a great addition to my home lab. Now I have a place to develop and test automation without breaking production 🙂</p>
<p>Anyways - I had a requirement to monitor my SRX clusters for route changes. Specifically I&rsquo;m using this with devices where I have implemented customer peering via BGP. The SRXs don&rsquo;t natively offer any form of monitoring and alerting for this, and the current monitoring applications at my disposal don&rsquo;t either. So I decided to write something myself, which took significantly less time than I had assumed.</p>
<p>Code has been posted up to my <a href="https://github.com/0x2142/juniper-srx-scripts/blob/master/checkBGP.py">GitHub</a> - but I&rsquo;m going to walk through some of it here. This script is intended to run as a cron job on a 5 or 10 minute interval.</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"># Imports</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">jnpr.junos</span> <span class="kn">import</span> <span class="n">Device</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">jnpr.junos.op.routes</span> <span class="kn">import</span> <span class="n">RouteTable</span>
</span></span></code></pre></div><p>On my initial research to see how easy this would be to pull off, I found a nifty thing in the <a href="/getting-started-with-junos-pyez/">JunOS PyEZ</a> package. Turns out they already offer a module literally called RouteTable that can pull the information I need.</p>
<p>Originally, I spent a bit of time trying to figure out how to use the standard device API to pull this info, but this module made everything 10x easier.</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">#################</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Required values</span>
</span></span><span class="line"><span class="cl"><span class="c1">#################</span>
</span></span><span class="line"><span class="cl"><span class="n">deviceName</span> <span class="o">=</span> <span class="s1">&#39;SRXFirewall&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">deviceIP</span> <span class="o">=</span> <span class="s1">&#39;0.0.0.0&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">apiuser</span> <span class="o">=</span> <span class="s1">&#39;username&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">apipassword</span> <span class="o">=</span> <span class="s1">&#39;password&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">SMTP</span> <span class="o">=</span> <span class="s1">&#39;smtp-alias&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">fromName</span> <span class="o">=</span> <span class="s1">&#39;BGP Monitor&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">fromAddr</span> <span class="o">=</span> <span class="s1">&#39;bgpmonitor@domain.com&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">toName</span> <span class="o">=</span> <span class="s1">&#39;contactName&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">toAddr</span> <span class="o">=</span> <span class="s1">&#39;contact@domain.com&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">prefixDict</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;FriendlyName&#34;</span><span class="p">:</span> <span class="s2">&#34;Prefix&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Route-to-Inet&#34;</span><span class="p">:</span> <span class="s2">&#34;0.0.0.0/0&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">################</span>
</span></span></code></pre></div><p>The section above contains a list of the required variables for this script to function. A lot of them are going to be self-explanatory - but I wanted to take a moment to look at the prefixDict. This is a Python dictionary that maps a friendly name to a route prefix, which is used when we check our routing table. For example, if you wanted to monitor the SRX device for a route for 10.10.10.0/24 which belongs to Customer1, then we would just add and entry in this dictionary for &ldquo;Customer1&rdquo;: &ldquo;10.10.10.0/24&rdquo;</p>
<p>Alright - now let&rsquo;s skip to the good stuff:</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"># Function to check received BGP routes</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">checkBGP</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Open SRX session</span>
</span></span><span class="line"><span class="cl">        <span class="n">dev</span><span class="o">.</span><span class="n">open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Pull device routing table, then keep only BGP originated routes</span>
</span></span><span class="line"><span class="cl">    <span class="n">allroutes</span> <span class="o">=</span> <span class="n">RouteTable</span><span class="p">(</span><span class="n">dev</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">bgp</span> <span class="o">=</span> <span class="n">allroutes</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">protocol</span><span class="o">=</span><span class="s2">&#34;bgp&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Close SRX session</span>
</span></span><span class="line"><span class="cl">    <span class="n">dev</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span></code></pre></div><p>The section above is the beginning of the checkBGP function. Simple enough - just open an API session to the SRX and grab the entire routing table. That module I was talking about earlier makes it super easy! Next, we pull only the routes originating from BGP and assign them to a variable called <code>bgp</code>.</p>
<p>So in order to run this script repeatedly as a cron, I needed a place to persistently store the last retrieved routing info. For the time being, this is done by simply writing a temp file with the information:</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="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">tempfile</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">tempfile</span><span class="p">,</span> <span class="s1">&#39;w+b&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">a</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">a</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">bgp</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Local file used to keep track of BGP learned routes</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">tempfile</span><span class="p">,</span> <span class="s1">&#39;ab&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">a</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">lastroutes</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Compare if routes are different</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">str</span><span class="p">(</span><span class="n">bgp</span><span class="p">)</span> <span class="o">==</span> <span class="nb">str</span><span class="p">(</span><span class="n">lastroutes</span><span class="p">[</span><span class="mi">0</span><span class="p">]):</span>
</span></span><span class="line"><span class="cl">            <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">str</span><span class="p">(</span><span class="n">bgp</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">str</span><span class="p">(</span><span class="n">lastroutes</span><span class="p">[</span><span class="mi">0</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="c1"># Delete file, then re-create with new route list</span>
</span></span><span class="line"><span class="cl">    <span class="c1">#os.remove(tempfile)</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">tempfile</span><span class="p">,</span> <span class="s1">&#39;w+b&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">a</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">bgp</span><span class="p">))</span>
</span></span></code></pre></div><p>The logic in the comparison is simple enough. If the current string of routes pulled from the SRX equals what is in the temp file (from the last run), then we assume no changes have occurred - and the script ends. Otherwise, if they don&rsquo;t match then something has changed.</p>
<p>Once we know something has changed, we&rsquo;ll go ahead and find out exactly which route entry is missing:</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"># Create Status list, by checking received routes in bgp object</span>
</span></span><span class="line"><span class="cl">    <span class="n">status</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span><span class="n">prefix</span> <span class="ow">in</span> <span class="n">prefixDict</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">prefix</span> <span class="ow">in</span> <span class="n">bgp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">status</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">%s</span><span class="s2"> - RECEIVED&#34;</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">prefix</span> <span class="ow">in</span> <span class="n">bgp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">status</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">%s</span><span class="s2"> - MISSING&#34;</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
</span></span></code></pre></div><p>This is where our prefixDict from earlier comes into play. We&rsquo;ll look at every BGP prefix that we defined in that dictionary, and see if it exists in the current SRX routing table. All of this information (both routes received and missing) get added to an alert email.</p>
<p>Lastly, we&rsquo;ll go ahead and compose our alert email to send out:</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"># Send alert message</span>
</span></span><span class="line"><span class="cl">    <span class="n">sendMail</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">lastroutes</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="nb">str</span><span class="p">(</span><span class="n">bgp</span><span class="p">),</span> <span class="n">status</span><span class="p">)</span>
</span></span></code></pre></div><p>I&rsquo;m not going to cover the email function here, since it&rsquo;s pretty straightforward.</p>
<p>That&rsquo;s it! The existence of the JunOS RouteTables module made creating this script a piece of cake. I&rsquo;m considering adding onto this later with some additional functionality, so be sure to check my <a href="https://github.com/0x2142/juniper-srx-scripts">GitHub</a> repo if you&rsquo;re interested.</p>
<p>Hope this is useful to someone else out there - Let me know in the comments!</p>
]]></content:encoded>
    </item>
    <item>
      <title>SRX High CPU: httpd</title>
      <link>https://0x2142.com/srx-httpd-high-cpu/</link>
      <pubDate>Tue, 05 Sep 2017 08:00:12 +0000</pubDate>
      <guid>https://0x2142.com/srx-httpd-high-cpu/</guid>
      <description>How to manage stuck httpd sessions that are causing 100% CPU utilization</description>
      <content:encoded><![CDATA[<p>Over the past few years of my Juniper SRX adventures, I&rsquo;ve run into a few cases where the Routing Engine (RE) CPU is pegged at 100%. From what I&rsquo;ve seen so far, this is typically one of three causes: high traffic (spike in IPS inspection), logging using event mode, or a stuck web management session.</p>
<p>In a few occasional cases, the CPU issue doesn&rsquo;t resolve itself and someone needs to manually investigate the cause. Luckily, the httpd issue is pretty easy to spot and fix - so I wanted to cover that briefly today. This issue can crop up randomly after someone uses the JWeb GUI to administer an SRX firewall. You could avoid this issue entirely by disabling the web interface entirely - but that&rsquo;s not always possible.</p>
<p>So the first thing we want to do is log into our SRX firewall and check the current CPU utilization for our RE processor:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{primary:node0}
</span></span><span class="line"><span class="cl">root@test-srx&gt; show chassis routing-engine node 0 
</span></span><span class="line"><span class="cl">node0:
</span></span><span class="line"><span class="cl">--------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">Routing Engine status:
</span></span><span class="line"><span class="cl">    Temperature                  41 degrees C / 105 degrees F
</span></span><span class="line"><span class="cl">    CPU temperature              70 degrees C / 158 degrees F
</span></span><span class="line"><span class="cl">    Total memory               4096 MB Max 1556 MB used ( 38 percent)
</span></span><span class="line"><span class="cl">      Control plane memory     2976 MB Max 804 MB used ( 27 percent)
</span></span><span class="line"><span class="cl">      Data plane memory        1120 MB Max 773 MB used ( 69 percent)
</span></span><span class="line"><span class="cl">    5 sec CPU utilization:
</span></span><span class="line"><span class="cl">      User                       41 percent
</span></span><span class="line"><span class="cl">      Background                  0 percent
</span></span><span class="line"><span class="cl">      Kernel                     59 percent
</span></span><span class="line"><span class="cl">      Interrupt                   0 percent
</span></span><span class="line"><span class="cl">      Idle                        0 percent
</span></span><span class="line"><span class="cl">    Model                           RE-SRX345
</span></span><span class="line"><span class="cl">    Serial ID                       XX1000XX0002
</span></span><span class="line"><span class="cl">    Start time                      2016-09-01 02:49:50 UTC
</span></span><span class="line"><span class="cl">    Uptime                          351 days, 13 hours, 28 minutes, 47 seconds
</span></span><span class="line"><span class="cl">    Last reboot reason              0x1:power cycle/failure
</span></span><span class="line"><span class="cl">    Load averages:                  1 minute   5 minute   15 minute
</span></span><span class="line"><span class="cl">                                        1.29       1.27        1.10
</span></span></code></pre></div><p>So we can see that over the past 5 seconds, there is 0% idle CPU - It&rsquo;s all split between User and Kernel. Some higher-end SRX models will also show utilization for 1 minute, 5 minutes, and 15 minutes.</p>
<p>Next, we want to confirm which process is consuming that CPU:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{primary:node0}
</span></span><span class="line"><span class="cl">root@test-srx&gt; show system processes extensive node 0
</span></span><span class="line"><span class="cl">node0:
</span></span><span class="line"><span class="cl">--------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">last pid: 25330;  load averages:  1.16,  1.24,  1.10  up 351+13:29:51    16:19:11
</span></span><span class="line"><span class="cl">165 processes: 21 running, 132 sleeping, 12 waiting
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Mem: 354M Active, 191M Inact, 1253M Wired, 585M Cache, 112M Buf, 1595M Free
</span></span><span class="line"><span class="cl">Swap:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  PID USERNAME     THR PRI NICE   SIZE    RES STATE  C   TIME   WCPU COMMAND
</span></span><span class="line"><span class="cl">1635 root           7  76    0  1192M   113M RUN    0    ??? 281.93% flowd_octeon_hm
</span></span><span class="line"><span class="cl">14607 nobody         3  76    0 14848K  6308K ucondt 0  25:03 83.45% httpd
</span></span><span class="line"><span class="cl">   21 root           1 171   52     0K    16K RUN    0 6952.9  0.00% idle: cpu0
</span></span><span class="line"><span class="cl"> 1679 root           1  76    0 48580K 24476K select 0  90.2H  0.00% mib2d
</span></span><span class="line"><span class="cl"> 1715 root           1  76    0 35264K 19520K select 0  49.0H  0.00% snmpd
</span></span><span class="line"><span class="cl">   23 root           1 -20 -139     0K    16K RUN    0  29.9H  0.00% swi7: clock
</span></span><span class="line"><span class="cl"> 1681 root           1   4    0   101M 68284K kqread 0  28.0H  0.00% rpd
</span></span><span class="line"><span class="cl">   22 root           1 -40 -159     0K    16K WAIT   0  26.0H  0.00% swi2: netisr 0
</span></span><span class="line"><span class="cl">  &lt;-- Output Truncated --&gt;
</span></span></code></pre></div><p>In this case it&rsquo;s pretty clear that httpd is the top offender for CPU usage. You might also notice the process named &lsquo;flowd_octeon_hm&rsquo;. This is part of the firewall processes, so don&rsquo;t be surprised if this process is also one of the top. It&rsquo;s pretty normal for this process to show &gt;100% CPU, so this is safe to ignore. If you see eventd as a top consumer, then you might have your logging configured to use event mode rather than stream mode - which I&rsquo;ll cover in another post.</p>
<p>So how do we fix the httpd problem? Reboot the SRX? Well, yeah that would probably fix it - but there is an easier way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{primary:node0}
</span></span><span class="line"><span class="cl">root@test-srx&gt; restart web-management
</span></span><span class="line"><span class="cl">Web management gatekeeper process started, pid 25343
</span></span></code></pre></div><p>One quick command and we&rsquo;ve restarted all of the web management processes, including httpd. So now you&rsquo;ll want to give the SRX a few seconds to recover itself - then run the <em>show system processes extensive</em>command again:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{primary:node0}
</span></span><span class="line"><span class="cl">root@test-srx&gt; show chassis routing-engine node 0
</span></span><span class="line"><span class="cl">node0:
</span></span><span class="line"><span class="cl">--------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">Routing Engine status:
</span></span><span class="line"><span class="cl">    Temperature                 41 degrees C / 105 degrees F
</span></span><span class="line"><span class="cl">    CPU temperature             69 degrees C / 156 degrees F
</span></span><span class="line"><span class="cl">    Total memory              4096 MB Max  1556 MB used ( 38 percent)
</span></span><span class="line"><span class="cl">      Control plane memory    2976 MB Max   804 MB used ( 27 percent)
</span></span><span class="line"><span class="cl">      Data plane memory       1120 MB Max   773 MB used ( 69 percent)
</span></span><span class="line"><span class="cl">    5 sec CPU utilization:
</span></span><span class="line"><span class="cl">      User                       6 percent
</span></span><span class="line"><span class="cl">      Background                 0 percent
</span></span><span class="line"><span class="cl">      Kernel                     3 percent
</span></span><span class="line"><span class="cl">      Interrupt                  0 percent
</span></span><span class="line"><span class="cl">      Idle                      91 percent
</span></span><span class="line"><span class="cl">    Model                          RE-SRX345
</span></span><span class="line"><span class="cl">    Serial ID                      XX1000XX0002
</span></span><span class="line"><span class="cl">    Start time                     2016-09-01 02:49:50 UTC
</span></span><span class="line"><span class="cl">    Uptime                         351 days, 13 hours, 32 minutes, 52 seconds
</span></span><span class="line"><span class="cl">    Last reboot reason             0x1:power cycle/failure
</span></span><span class="line"><span class="cl">    Load averages:                 1 minute   5 minute  15 minute
</span></span><span class="line"><span class="cl">                                       0.35       0.99       1.04
</span></span></code></pre></div><p>Looks much better, with 91% idle CPU!</p>
<p>Even though this issue can be annoying, its a quick fix - I recommend that you perform some sort of CPU monitoring/alerting on your SRX clusters (I use <a href="http://www.observium.org">Observium</a> for this). This will help to identify the issue quickly and then get it resolved quickly. If this issue is left unchecked, it can sometimes cause some latency and performance issues.</p>
<p>Hope this helps!</p>
]]></content:encoded>
    </item>
    <item>
      <title>SRX Basics: Redunancy Groups and Failover</title>
      <link>https://0x2142.com/srx-basics-redunancy-groups-and-failover/</link>
      <pubDate>Tue, 18 Jul 2017 08:00:24 +0000</pubDate>
      <guid>https://0x2142.com/srx-basics-redunancy-groups-and-failover/</guid>
      <description>Some background &amp;amp; configuration examples for Juniper SRX redundancy groups</description>
      <content:encoded><![CDATA[<p>In last weeks post, we took a look at how to set up a <a href="/srx-basics-clustering/">chassis cluster</a> on a Juniper SRX Firewall. So now that we have a basic cluster setup - Let&rsquo;s explore some of the additional options and configuration items.</p>
<h2 id="redundant-ethernet-interfaces">Redundant Ethernet Interfaces</h2>
<p>So first thing is first - Once you have a cluster configured, you&rsquo;ll probably want to configure a few sets of redundant ethernet interfaces. These interfaces are also often referred to as reth interfaces. This will create a shared interface between your SRX pair, where you can configure IP address and VLAN information to be shared between the two. Let&rsquo;s say that we have a Juniper SRX 1500 cluster, and we want to create a redundant interface for one of our 10Gb ports. Here is how we would do that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# set interfaces xe-0/0/16 gigether-options redundant-parent reth1
</span></span><span class="line"><span class="cl">root@testsrx# set interfaces xe-7/0/16 gigether-options redundant-parent reth1
</span></span><span class="line"><span class="cl">root@testsrx# set interfaces reth1 redundant-ether-options redundancy-group 1
</span></span></code></pre></div><p>In the config above, we first take both of our interfaces (xe-0/0/16 on node0, and xe-7/0/16 on node1) and tell them that they now belong to a redundant interface group (reth1). Next, we enter into the reth1 config, and associate it to a redundancy group.</p>
<p>You&rsquo;re also going to need to keep in mind that the SRX requires you to specify how many redundant ethernet interfaces will be configured. This is likely a memory thing, since each SRX also has a different maximum number of reth interfaces that can be configured. For example, if you tell the SRX that you need 5 reth interfaces, then the SRX will allocate system resources to manage those interfaces. In order to set the number of available reth interfaces, we&rsquo;ll use the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# set chassis cluster reth-count 5
</span></span></code></pre></div><h2 id="redundancy-groups">Redundancy Groups</h2>
<p>A redundancy group, or RG, is used as a container for logically grouping redundant interfaces/virtual routers which must fail over together. A single RG can be configured as primary on one of the two active SRX firewalls is a cluster - with the ability to fail over to the other node. For example, we might want be planning on only using one virtual routing instance on our SRX - so we would create RG1 and assign out interfaces to belong to it.</p>
<p>A quick note - all interfaces in a single virtual router must belong to the same RG. This way the virtual routing instance and all of it&rsquo;s associated interfaces will always run on the same SRX node. In order to achieve an active/active firewall configuration, you would need to create two separate virtual routers, each with their own reth interfaces and different RGs. Then you would make RG1 primary on node0, and RG2 primary on node1.</p>
<p>In most configurations, dumping all of your reth interfaces into RG1 will be sufficient. You&rsquo;re likely going to want to set up a priority for each RG - and maybe even preemptive fail-over. In order to do that - you&rsquo;ll have to configure each cluster member with a priority:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 0 node 0 priority 200
</span></span><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 0 node 1 priority 50
</span></span><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 1 node 0 priority 200
</span></span><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 1 node 1 priority 50
</span></span><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 1 preempt
</span></span></code></pre></div><p>The higher priority wins here - so if you set node0 to a higher priority and preempt is enabled, then node0 will actively try to take ownership of RG1. I would rather not set preempt on RG0 for a few reasons - which we&rsquo;ll cover in the next section. Priorities can also be modified using interface monitoring, so if a particular interface goes offline we can decrement the priority of that node (also covered below).</p>
<h2 id="a-note-about-rg0">A Note About RG0</h2>
<p>You might notice from the last post, that you&rsquo;re output of <strong>show chassis cluster status</strong> already showed two redundancy groups: RG0 and RG1. RG0 is only used for management traffic and manages the routing engine for your SRX. Unfortunately, this can lead to some weird behaviors that you might not be expecting.</p>
<p>For example, whichever node is primary for RG0 is the only node that collects interface and monitoring statistics. If you&rsquo;re using a monitoring tool that polls data from both of your SRXs, then the secondary for RG0 will report <strong>nothing</strong> about it&rsquo;s interfaces, CPU, etc. This is also true if you log into the actual SRX itself - a <strong>show interfaces</strong> will actually return a bunch of default values, including showing that your ports are half-duplex. Don&rsquo;t panic though, this is just an oddity of RG0. If you log back into the primary node for RG0, then it will show all of the proper statistics for both SRX firewalls.</p>
<p>Due to these weird things about RG0 - I prefer to always leave it on node0. Therefore I know which one to log into whenever I need to look at something, or which SRX to check in our monitoring tools. It&rsquo;s also worth noting that whichever SRX is primary for RG0 is also the node you&rsquo;re going to need to log into for configuration changes - even if all of your other redundancy groups are the other SRX.</p>
<p>Weird, right?</p>
<p>Oh, and be warned that since RG0 controls the routing engine, a failover of this RG can cause brief outages. This is primarily because the routing table and firewall state information will be lost. The secondary node has to spin up new processes for the routing engine, and at least currently there isn&rsquo;t a graceful sync of all of that data.</p>
<h2 id="interface-monitoring">Interface Monitoring</h2>
<p>I mentioned setting device priorities a bit earlier. Setting interface weights is going to be the primary method for dynamically affecting those priorities, and therefore possibly causing a preemptive failover. One example might be that you&rsquo;re using an SRX cluster for your edge firewall, and you want it to automatically fail over if the primary loses it&rsquo;s internet uplink.</p>
<p>Note that you must configure the <em>physical</em> interfaces here, not the redundant ethernet interfaces:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# set chassis cluster redundancy-group 1 interface-monitor xe-0/0/16 weight 160
</span></span></code></pre></div><p>Remember when we set the priorities of our firewalls earlier? Node0 was set to 200, and node1 at 50. So here we are saying that xe-0/0/16 on node0 is worth 160 points. So if xe-0/0/16 goes down, then node0 will decrement it&rsquo;s priority by 160 - which will be 40. This will trigger a preemtive failover by node1. The reverse is also true - when xe-0/0/16 comes back up, then node0&rsquo;s priority will go back up to 200. Then node0 will take back ownership of RG1.</p>
<h2 id="manual-failover">Manual Failover</h2>
<p>There is a pretty good chance at some point you might need to perform a manual failover of your SRX redundancy groups. Maybe you need to do some maintenance or upgrades, or you just want to make sure failover works as you expect. In either case, the commands to do this are pretty straightforward:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; show chassis cluster status
</span></span><span class="line"><span class="cl">Cluster ID: 5
</span></span><span class="line"><span class="cl">Node       Priority       Status       Preempt       Manual failover
</span></span><span class="line"><span class="cl">Redundancy group: 0 , Failover count: 0
</span></span><span class="line"><span class="cl">node0      200            primary      no            no 
</span></span><span class="line"><span class="cl">node1      50             secondary    no            no
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Redundancy group: 1 , Failover count: 0
</span></span><span class="line"><span class="cl">node0      200            primary      yes           no 
</span></span><span class="line"><span class="cl">node1      50             secondary    yes           no
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">root@testsrx&gt; request chassis cluster failover redundancy-group 1 node 1 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">root@testsrx&gt; show chassis cluster status 
</span></span><span class="line"><span class="cl">Cluster ID: 5
</span></span><span class="line"><span class="cl">Node       Priority       Status       Preempt       Manual failover
</span></span><span class="line"><span class="cl">Redundancy group: 0 , Failover count: 0
</span></span><span class="line"><span class="cl">node0     200             primary      no            no 
</span></span><span class="line"><span class="cl">node1     50              secondary    no            no
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Redundancy group: 1 , Failover count: 1
</span></span><span class="line"><span class="cl">node0     200             secondary    yes           yes
</span></span><span class="line"><span class="cl">node1     255             primary      yes           yes
</span></span></code></pre></div><p>Okay - so let&rsquo;s talk about a few things that have happened here. I always recommend that you run a <strong>show chassis cluster status</strong> first, so you know where things already stand. Then we can proceed by requesting a failover. To do this, you have to specify which redundancy group you want to fail over, and which node you want to become the new primary. So in this case, we made node1 the new primary of RG1.</p>
<p>You might also notice that the priorities have changed, and the devices are marked as being in a manual failover state. This is important, because <strong>you cannot manually fail back until you reset this state</strong>. That&rsquo;s right - if you tried to run the failover command again to move RG1 back to node0, it will not work. An automatic failover due to hardware failure or interface monitoring will still be permitted. In order to perform a manual fail-back to node0, we have to run the following reset command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; request chassis cluster failover reset redundancy-group 1
</span></span></code></pre></div><hr>
<p>Hopefully between last weeks post and this one, you should have a good handle on the basics of configuring a chassis cluster on your new pair of Juniper SRX firewalls. Let me know in the comments below if this helped you!</p>
]]></content:encoded>
    </item>
    <item>
      <title>SRX Basics: Clustering</title>
      <link>https://0x2142.com/srx-basics-clustering/</link>
      <pubDate>Tue, 11 Jul 2017 08:00:07 +0000</pubDate>
      <guid>https://0x2142.com/srx-basics-clustering/</guid>
      <description>A short tutorial on how to configure Juniper SRX clustering</description>
      <content:encoded><![CDATA[<p>So you just unboxed a brand new pair of Juniper SRX firewalls - now what? Well, the first thing you&rsquo;re likely going to want to do is get the two devices hooked up and clustered together. That should be pretty simple, right? Yeah, mostly - though there are a few variations between device models, and there are a few fine-print steps that might keep you from getting everything working the first time.</p>
<p>So let&rsquo;s take a look at what we need to do!</p>
<h2 id="physical-configuration">Physical configuration</h2>
<p>First thing we need to do is get both devices unboxed and cabled appropriately. In order to get a successful cluster configured, we will need to get two critical ports connected: the HA control port and the cluster fabric port. Technically only the HA control port is required to get a cluster working, but you&rsquo;ll want to get the fabric port working as well - here is what both ports are used for:</p>
<p><strong>HA Control Port</strong> - This is used for communication between the cluster members. This connection is used just for control-plane stuff - like keepalives/heartbeats and config sync between the two nodes.</p>
<p><strong>Fabric Port</strong> - This port is used for data sync between the cluster members. All routing/firewall state information is synced using this port, and any cross-cluster traffic is also transferred using this port (for example, if one SRX was primary for a redundancy group, but the secondary was the active BGP speaker for your upstream connection - then the traffic would come in through the secondary and cross this link to reach the primary RG)</p>
<p>The fabric port is the easiest to connect - because you can use any port you like, then specify which port to use in the CLI. The control port, however, must be the assigned port that Juniper allocates for this use. Unfortunately, this port varies between device models. The two most common SRXs that I&rsquo;ve deployed are the 345 and 1500. The SRX 1500 has a dedicated 10G HA control port, but the SRX 345 actually uses ge-0/0/1 on both nodes for this. Juniper lists what all those port assignments are over on <a href="https://kb.juniper.net/InfoCenter/index?page=content&amp;id=KB15356&amp;actp=METADATA">this page</a>.</p>
<p>Once those ports are connected, go ahead and power on both devices!</p>
<h2 id="junos-config">JunOS Config</h2>
<p>Okay - Once the physical configuration has been completed, there are a few things that need to be configured on both devices before you can establish a cluster.</p>
<p>When you first boot each device, you&rsquo;ll log in with <strong>root</strong> and no password. Then you&rsquo;ll be dropped into the JunOS shell, and you&rsquo;ll need to type <strong>cli</strong> to start the JunOS command-line interface. Then type <strong>configure</strong> to get into the configuration mode.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx% cli
</span></span><span class="line"><span class="cl">root@testsrx&gt; configure
</span></span><span class="line"><span class="cl">root@testsrx#
</span></span></code></pre></div><p>In the config mode, we&rsquo;ll need to set a root password before we can enable the clustering. This password must match on both devices!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# set system root-authentication plain-text-password
</span></span><span class="line"><span class="cl">New Password: &lt;type your root password here&gt;
</span></span><span class="line"><span class="cl">Retype new password: &lt;and again...&gt;
</span></span></code></pre></div><p>For the SRX 1500 series, where there is a dedicated HA Control port, this is enough to get the cluster working. But for some of the branch SRXs, like the 300 series, you&rsquo;ll need to make a few additional changes. These devices come with a default config, which includes IP addresses on certain interfaces. Unfortunately, this will conflict with your cluster config and will not allow your cluster to reach a healthy state.</p>
<p>In my config, I already plan on re-configuring all of the interfaces and security-zones to fit my needs - so I will just delete those entire config sections:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# delete interfaces
</span></span><span class="line"><span class="cl">root@testsrx# delete security
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">After all that is done, we need to commit our changes:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">```text
</span></span><span class="line"><span class="cl">root@testsrx# commit
</span></span></code></pre></div><p>Finally we can go ahead and set up the cluster! This config is actually done outside of configure mode, so you will need to exit that.</p>
<p>So one thing to note here - each cluster will be configured with a cluster-id. This <strong>MUST</strong>be unique across any layer 2 subnet. So if we had multiple SRX clusters within a single broadcast domain, we would need to assign each one a different cluster ID.  I&rsquo;ll use cluster-id 5 in this example.</p>
<p>On whichever SRX you want to be the primary node:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx# exit
</span></span><span class="line"><span class="cl">root@testsrx&gt; set chassis cluster cluster-id 5 node 0 reboot
</span></span></code></pre></div><p>I personally like to give the primary a minute or to into the boot process before I configure the secondary, but we&rsquo;ll do so with a similar command (just specifying node 1 instead of 0):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; set chassis cluster cluster-id 5 node 1 reboot
</span></span></code></pre></div><p>After both nodes come back online, log into node 0 and run the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; show chassis cluster status 
</span></span><span class="line"><span class="cl">Cluster ID: 5
</span></span><span class="line"><span class="cl">Node       Priority      Status      Preempt      Manual failover
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Redundancy group: 0 , Failover count: 0
</span></span><span class="line"><span class="cl">node0      100           primary     no           no 
</span></span><span class="line"><span class="cl">node1      100           secondary   no           no
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Redundancy group: 1 , Failover count: 0
</span></span><span class="line"><span class="cl">node0      100           primary     yes          no 
</span></span><span class="line"><span class="cl">node1      100           secondary   yes          no
</span></span></code></pre></div><p>Perfect! Now let&rsquo;s go configure our fabric ports! Interface fab0 will be configured as the fabric port on node0, and interface fab1 will be configured as the fabric port on node1.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; configure
</span></span><span class="line"><span class="cl">root@testsrx# set interface fab0 fabric-options member-interfaces ge-0/0/10
</span></span><span class="line"><span class="cl">root@testsrx# set interface fab1 fabric-options member-interfaces ge-5/0/10
</span></span><span class="line"><span class="cl">root@testsrx# commit
</span></span></code></pre></div><p>Now that we&rsquo;re in a cluster, all of this configuration can be done on node0 - but note that in this case the secondary device&rsquo;s ports all start with ge-5/x/x. This is another oddity of JunOS - that numbering scheme isn&rsquo;t always the case. In the SRX1500s, the node1 ports all start with ge-7/x/x - so this will vary depending on what devices you&rsquo;re working with. If you ever need to check this - you can run <strong>show interface terse</strong> to list all interfaces in the cluster.</p>
<p>As a final verification that all our ports are up, drop out of config mode and run <strong>show chassis cluster interfaces:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">root@testsrx&gt; show chassis cluster interfaces
</span></span><span class="line"><span class="cl">Control link 0 name: ge-0/0/1
</span></span><span class="line"><span class="cl">Control link status: Up
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fabric interfaces:
</span></span><span class="line"><span class="cl">Name Child-interface Status
</span></span><span class="line"><span class="cl">fab0 ge-0/0/10 up
</span></span><span class="line"><span class="cl">fab0
</span></span><span class="line"><span class="cl">fab1 ge-5/0/10 up
</span></span><span class="line"><span class="cl">fab1
</span></span><span class="line"><span class="cl">Fabric link status: up
</span></span></code></pre></div><p>Hooray! We now have a functioning SRX cluster!</p>
<p>Sometimes if this doesn&rsquo;t work, the output of <strong>show chassis cluster status</strong> will show the secondary node as <strong>disabled</strong> or <strong>lost.</strong> I&rsquo;ve found that <strong>lost</strong> usually indicates a conflicting configuration on the cluster interfaces (like leaving the default IPs configured). If you see <strong>disabled</strong>, try rebooting the secondary node again - and if that doesn&rsquo;t work, then you may need to disable clustering on both nodes and re-configure. This can be done using the <strong>set chassis cluster disable reboot</strong> command.</p>
<p>Next week, we&rsquo;ll look at redundancy-groups, performing manual failovers, and setting up interface monitoring for automatic failovers. Hope this was helpful!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 4 – Polling the SRX)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/</link>
      <pubDate>Tue, 04 Jul 2017 08:00:31 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/</guid>
      <description>This post focuses on the Juniper SRX side of my VPN dashboard - writing the Python scripting to query VPN status from each device</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior posts first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
</ul>
<hr>
<p>Alright - We&rsquo;re finally at the last step to this project. We already have Django running with a somewhat decent-looking web dashboard. Now the only thing that remains is polling our SRX firewalls for VPN connections, then populating this information into the database.</p>
<p>For this to happen, I built a custom management command for Django - the cool thing here is that we can easily use this within a cron job to automatically schedule updates at regular intervals. So within our application (the vpn folder), we&rsquo;re going to create a <em>management</em> folder then a <em>commands</em> folder within that. In here you can name your script whatever you want, so I went with cronpoller.py.</p>
<p>The script that I wrote is extremely simplistic and could probably use quite a number of improvements - which I will slowly make over time. But for now, it is what it is - and it does exactly what I need it to.</p>
<p>First thing we need to do is import a few things we will need:</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">django.core.management.base</span> <span class="kn">import</span> <span class="n">BaseCommand</span><span class="p">,</span> <span class="n">CommandError</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">vpn.models</span> <span class="kn">import</span> <span class="n">Firewall</span><span class="p">,</span> <span class="n">Datacenter</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">jnpr.junos</span> <span class="kn">import</span> <span class="n">Device</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">lxml</span> <span class="kn">import</span> <span class="n">etree</span>
</span></span></code></pre></div><p>We have some Django modules for treating this as a management command - and we&rsquo;re also importing our existing models into this script. This part is really cool to me, because it means that this cron script can just reference the existing objects that we created to get their attributes. Next, we import the JunOS stuff from their pyEZ packages (don&rsquo;t forget to <a href="/getting-started-with-junos-pyez/">install pyEZ</a>!). The last one is going to be used to parse the responses from our SRX into something we can use.</p>
<p>Alright - so in order to build a management command, we have to create a class called <em>Command,</em> then define our functions within that. Within the <em>Command</em> class, Django will look for a function called <em>handle</em> to execute. In my final script, I have that function plus one called <em>getVPNStatus</em>. Let&rsquo;s start with putting together <em>getVPNStatus:</em></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"># This function will poll the device for status </span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">getVPNStatus</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fw</span><span class="p">,</span> <span class="n">dc</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">connectedlist</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># So we generate a JunOS pyEZ device connection using the information about the </span>
</span></span><span class="line"><span class="cl">    <span class="c1"># firewall that we gather from the database object</span>
</span></span><span class="line"><span class="cl">    <span class="n">device</span> <span class="o">=</span> <span class="n">Device</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_manageip</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">user</span><span class="o">=</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_user</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">password</span><span class="o">=</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_pass</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Try to open a connection out to the target device</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">dev</span><span class="o">.</span><span class="n">open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># If for some reason this doesn&#39;t work, just return UNREACHABLE - which</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we&#39;ll assume means the device is down</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;UNREACHABLE&#34;</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">    <span class="c1"># Here is where we poll the SRX for a list of all IPSec Security Associations.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># The equivalent of the &#39;show security ipsec sa&#39; command</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">etree</span><span class="o">.</span><span class="n">tostring</span><span class="p">(</span><span class="n">dev</span><span class="o">.</span><span class="n">rpc</span><span class="o">.</span><span class="n">get_security_associations_information</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># The SRX returns a response in XML, which we&#39;ll need to dig through</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Credit to the guys over at &lt;a href=&#34;http://packetpushers.net/parsing-junos-xml-python/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Packet Pushers&lt;/a&gt; for a great post explaining how to</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># parse these responses</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> <span class="k">as</span> <span class="n">a</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">xmldoc</span> <span class="o">=</span> <span class="n">etree</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">docroot</span> <span class="o">=</span> <span class="n">xmldoc</span><span class="o">.</span><span class="n">getroot</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">rootchildren</span> <span class="o">=</span> <span class="n">docroot</span><span class="o">.</span><span class="n">iter</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">child</span> <span class="ow">in</span> <span class="n">rootchildren</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># For each IPSec SA returned, we need to find the remote gateway IP, which </span>
</span></span><span class="line"><span class="cl">            <span class="c1"># we use to tie the connection back to the connected datacenter</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">child</span><span class="o">.</span><span class="n">tag</span> <span class="o">==</span> <span class="s2">&#34;sa-remote-gateway&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">connectedlist</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">child</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">    <span class="c1"># Once we&#39;ve built our list, send it back!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">connectedlist</span>
</span></span></code></pre></div><p>That already is major part of our script. It will connect out to each device, grab a list of every connected IPSec tunnel, then return back a list of connected gateways. Now we just need to write our <em>handle</em> function, which will do all of the remaining work.</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">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">options</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Let&#39;s quickly create two lists - based on our firewall and datacenter models</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This actually polls the database for anything that&#39;s been created</span>
</span></span><span class="line"><span class="cl">    <span class="n">dclist</span> <span class="o">=</span> <span class="n">Datacenter</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;datacenter_code&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">fwlist</span> <span class="o">=</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#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"># This will loop through each firewall, then call the getVPNStatus function</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">fw</span> <span class="ow">in</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">statuslist</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getVPNStatus</span><span class="p">(</span><span class="n">fw</span><span class="p">,</span> <span class="n">dclist</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Once we have our response, we&#39;re going to check the returned list of connected</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># gateways against our list of datacenters from the database</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">dc</span> <span class="ow">in</span> <span class="n">dclist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">remoteFW</span> <span class="ow">in</span> <span class="n">fwlist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># If we find another datacenter in the connected gateway list, add </span>
</span></span><span class="line"><span class="cl">            <span class="c1"># that datacenter to the vpnstatus list as &#34;VPNUP&#34;, otherwise assume it&#39;s down</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">remoteFW</span><span class="o">.</span><span class="n">firewall_vpnip</span> <span class="ow">in</span> <span class="n">statuslist</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">vpnstatus</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;VPNUP&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</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">vpnstatus</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;VPNDOWN&#34;</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">    <span class="c1"># After all that is done - we just save the new vpnstatus list to the firewall_vpnstatus</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># field in the database</span>
</span></span><span class="line"><span class="cl">    <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span> <span class="o">=</span> <span class="n">vpnstatus</span>
</span></span><span class="line"><span class="cl">    <span class="n">fw</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">update_fields</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;firewall_vpnstatus&#34;</span><span class="p">])</span>
</span></span></code></pre></div><p>I&rsquo;ve said this before, but I think it&rsquo;s worth noting again - This probably isn&rsquo;t the best or most efficient/reliable way of doing this. I think that over time I would like to revisit this and refine it a bit - but this is what I&rsquo;ve put together so far. In fact, I think it&rsquo;s worth stating that this will probably break in a fantastically horrific manner. This isn&rsquo;t a finished product, but pretty much just version 0.1 - the base functionality works, but it&rsquo;s in desperate need of refinement.</p>
<p>Alright - so now we just have to add a cron job in our system to call this script and run it. Depending on how many firewalls you have and how the latency is, you might need a different interval than I am using - but I went ahead and set mine to run once every 10 minutes. I would like to get this down to 5 minutes or less, but I might need to figure out a way to potentially multi-thread the cronpoller.py script. So here is what I did in /etc/cron.d/vpnstatuspoller:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">*/10  * * * * * python /root/junos-dashboard/manage.py cronpoller
</span></span></code></pre></div><p>After letting the script poll my firewalls - I ended up with a dashboard that looks like this:</p>
<p><img alt="dashboard-complete" loading="lazy" src="/content/images/2021/01/dashboard-complete.png#center"></p>
<h2 id="future-improvements">Future improvements</h2>
<p>Of course, I still have a few things I would like to add or improve on. I&rsquo;ve been keeping a list of some ideas that I&rsquo;ve had, which I&rsquo;ll share here:</p>
<ul>
<li>Add a timestamp to each firewall that contains the last poll time (in case something gets missed or hasn&rsquo;t updated yet)</li>
<li>Possibly add ability to click a &lsquo;down&rsquo; VPN cell to force clear SA</li>
<li>Set up email alerts from the cronpoller to automatically notify me of a failure</li>
<li>Ability to click on any VPN cell and get additional info - like VPN uptime, subnets routed across it, or maybe the IKE/IPSec negotiation parameters</li>
<li>Ability to click on any firewall name and get additional info - like uptime, JunOS version, CPU/Memory</li>
</ul>
<hr>
<p>I finally got around to getting myself a <a href="https://github.com/0x2142">GitHub</a> account, so I might put the final code up there once I&rsquo;m done. I also have a number of other JunOS scripts that are probably worth posting up there as well.</p>
<p>Well, I hope you enjoyed reading about this project - because I certainly enjoyed working on it. This was one of my first real projects using the JunOS pyEZ libraries, and I got to pick up and learn how to use Django as well. The experience of building this has given me ideas for other JunOS automation projects - so look out for some of those in the future!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 3 – Creating the Dashboard)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/</link>
      <pubDate>Tue, 27 Jun 2017 08:00:19 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/</guid>
      <description>This post focuses on the web GUI side of the VPN dashboard - storing &amp;amp; displaying the information that gets collected</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior posts first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
</ul>
<hr>
<p>In the last post, we got Django working well enough to start serving up web requests. That&rsquo;s a great start - So now it&rsquo;s on to actually building the project. We&rsquo;re going to cover this in two parts: the front-end dashboard, and the back-end scripts. In this post, I&rsquo;m going to show you how I began putting everything together to assemble the front-end HTML dashboard.</p>
<p>As a quick refresher note, we created a project in the last post called junos-dashboard. Within that project, we created our app named vpn. This folder is primarily where we&rsquo;ll be spending our time today. There are two files in our app folder that will need a few edits:</p>
<p><strong>views.py</strong> - This is where we originally created our basic index page, which currently just spits out a message. So we&rsquo;ll need to add code in here to render our dashboard instead.</p>
<p><strong>models.py</strong> - In here we will define our objects and the characteristics about those objects that we want to store/retrieve in the sqlite database.</p>
<p>Let&rsquo;s go ahead and open up our models.py file. For my dashboard, I have two objects I want to track - Firewalls (the SRX hosting the VPN) and Datacenter (as a location tracking). So in order to create those objects, we just have to add two classes into the models.py file:</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">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Datacenter</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">datacenter_code</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Firewall</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">firewall_name</span>
</span></span></code></pre></div><p>Easy enough, right? So here is the really cool thing about Django (or probably any web framework really) - These model objects are now something that we can reference in our code to change or query attributes about them. Even cooler is that Django automatically builds an admin page to our site, where we can create new instances of each model right there (which I&rsquo;ll cover later). So this means that all I have to do is define these two models and their attributes, then anyone on my team can log into the dashboard admin page and add new firewalls or datacenters.</p>
<p>Okay, so in order to actually make these models useful, we need to add our attributes. For the datacenter object, I only care about two things - What is the datacenter code (identifier), and is the datacenter an active location. So here is where we add those options under our datacenter class:</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">class</span> <span class="nc">Datacenter</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_code</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;Datacenter Code&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="s1">&#39;Datacenter Active?&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>The firewall object gets a little more complicated because I want to be able to define quite a few things. The obvious attributes are: the firewall name, which datacenter it belongs to, and whether or not it is an active firewall. I&rsquo;ll also need to collect this firewall&rsquo;s VPN peer IP, so that I can compare it against other devices to check the connection. But wait - how am I actually checking that VPN status? Oh, I guess I&rsquo;ll also need to collect a username/password for the SRX API, as well as a valid management IP to reach the API.</p>
<p>Let&rsquo;s take a moment though - because I stopped here and realized that I&rsquo;ll need to collect user/password data in a form, then it&rsquo;s going to be stored in a sqlite database. Oh, and of course it will be unencrypted. I didn&rsquo;t really like that idea - so I did some research on how to encrypt one of the object attributes. Turns out, it&rsquo;s actually pretty easy since someone has already created a module to do exactly that.</p>
<p>So let&rsquo;s grab that module real quick:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos vpn]# pip install django-encrypted-fields
</span></span></code></pre></div><p>Once we have the module, it requires us to generate some keys which will be used for encrypting our data. The following is based on the steps listed on the GitHub page for <a href="https://github.com/defrex/django-encrypted-fields">django-encrypted-fields</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# mkdir fieldkeys
</span></span><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# keyczart create --location=fieldkeys --purpose=crypt
</span></span><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# keyczart addkey --location=fieldkeys --status=primary --size=256
</span></span></code></pre></div><p>Next, we just need to add this keyfile into our settings.py:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ENCRYPTED_FIELDS_KEYDIR = &#39;/root/junos-dashboard/fieldkeys&#39;
</span></span></code></pre></div><p>Alright - back to actually creating the attributes we need for our firewalls. Since we&rsquo;re using this new module, we&rsquo;ll need to add an additional import statement - otherwise, we are just adding the fields I covered earlier:</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">encrypted_fields</span> <span class="kn">import</span> <span class="n">EncryptedCharField</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Firewall</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;Firewall Name&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_location</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="s1">&#39;Datacenter&#39;</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_active</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="s1">&#39;Firewall Active?&#39;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_manageip</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">GenericIPAddressField</span><span class="p">(</span><span class="s1">&#39;Management IP&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_vpnip</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">GenericIPAddressField</span><span class="p">(</span><span class="s1">&#39;VPN Interface IP&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_user</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s1">&#39;API User&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_pass</span> <span class="o">=</span> <span class="n">EncryptedCharField</span><span class="p">(</span><span class="s1">&#39;API Pass&#39;</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_vpnstatus</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">editable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>A few things to note here - The firewall_location attribute will actually query the table of datacenter objects - so we&rsquo;re not creating any data twice. You might also notice that I added a firewall_vpnstatus text field, which is going to be hidden in the administrative console. Because I&rsquo;m a terrible programmer, I&rsquo;ll be storing all of the VPN status info in this text field in the database. Yes, there is probably a much more elegant way of handling this - but again, I&rsquo;m a network admin not a professional coder. For what I need, this gets the job done. If you have a better way of accomplishing this - let me know! I would be very interested in another method.</p>
<p>I also found out later that just because I encrypted the field doesn&rsquo;t mean that Django knows the field is a password. I wanted the field to be treated like a password input, so the data wasn&rsquo;t visible to anyone who just logged in. I found out that you can create a forms.py file within the vpn directory, and pretty much override the field type to account for this.</p>
<p>So here is what I threw into the forms.py file to handle password input:</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">django.forms</span> <span class="kn">import</span> <span class="n">ModelForm</span><span class="p">,</span> <span class="n">PasswordInput</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Firewall</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FirewallForm</span><span class="p">(</span><span class="n">ModelForm</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_pass</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span><span class="n">widget</span><span class="o">=</span><span class="n">forms</span><span class="o">.</span><span class="n">PasswordInput</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span> <span class="o">=</span> <span class="n">Firewall</span>
</span></span><span class="line"><span class="cl">        <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;firewall_pass&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>That was actually way easier than I had anticipated.</p>
<p>Awesome, now our work on the models is completely done - why don&rsquo;t we log into the administrative  web interface and take a look at how it all turned out?</p>
<p>Here is a view of the datacenter page - where we can add a new location:</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/djangoadmin-datacenters.png#center"></p>
<p>And now for the firewall objects - with all the fields we need to get the job done:</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/djangoadmin-firewalls.png#center"></p>
<p>Looks pretty good, huh? And we didn&rsquo;t even need to do any work to get the free admin functionality! This is honestly my favorite part of Django. Without posting a ton of additional screenshots, the admin page also provides audit logs - so we can see who changed what objects. Very helpful.</p>
<hr>
<p>Alright - now let&rsquo;s start work on our views.py file, so we can actually start putting all this together in our dashboard!</p>
<p>Within our views.py file, we already had our index function, which just returned a string of text. We&rsquo;ll be adding to that in a moment - but first I created a separate function called buildrows, which does exactly what you think. This function will pull each firewall and it&rsquo;s status, and build our little HTML table. I&rsquo;m not going to go into great detail on this - but check out the comments for more information on what&rsquo;s going on here:</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"># This is our function to build the HTML table - We&#39;re going to be expecting this function</span>
</span></span><span class="line"><span class="cl"><span class="c1"># to be fed a list of firewalls and datacenters</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">buildrows</span><span class="p">(</span><span class="n">firewall_list</span><span class="p">,</span> <span class="n">datacenter_list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewallstatus</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We will iterate through each firewall and compile each row for our table</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">fw</span> <span class="ow">in</span> <span class="n">firewall_list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># The first cell in our row is going to be the firewall name and it&#39;s own VPN peer IP</span>
</span></span><span class="line"><span class="cl">        <span class="n">onerow</span> <span class="o">=</span> <span class="s2">&#34;&lt;td&gt;&lt;b&gt;</span><span class="si">%s</span><span class="s2">&lt;/b&gt;&lt;i&gt;</span><span class="si">%s</span><span class="s2">&lt;/i&gt;&lt;/td&gt;&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_name</span><span class="p">,</span> <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnip</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Now we iterate through every datacenter, in order to see which ones we have a</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># connection to from this firewall</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">dc</span> <span class="ow">in</span> <span class="n">datacenter_list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># First thing is first - If this firewall contains the name of the datacenter</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># then we&#39;ll print N/A, since we wouldn&#39;t expect a VPN to itself </span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="nb">str</span><span class="p">(</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;N/A&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Also, if there is no VPN status in the database, then just print &#39;No Status&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;No Status&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">     
</span></span><span class="line"><span class="cl">            <span class="c1"># This portion is pretty self-explanatory - We parse the vpnstatus field in the</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># database. If the state of the connection is &#39;VPNUP&#39;, then we print a cell for</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># that datacenter with the text &#39;UP&#39;. If the state is &#39;VPNDOWN&#39;, then we print</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># &#39;DOWN&#39;. And if nothing matches, print &#39;No Status&#39;</span>
</span></span><span class="line"><span class="cl">            <span class="n">statuslist</span> <span class="o">=</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="n">fw</span><span class="o">.</span><span class="n">firewall_vpnstatus</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">status</span> <span class="o">=</span> <span class="n">statuslist</span><span class="p">[</span><span class="n">dc</span><span class="o">.</span><span class="n">datacenter_code</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="s2">&#34;VPNUP&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td class=</span><span class="se">\&#34;</span><span class="s2">vpnup</span><span class="se">\&#34;</span><span class="s2">&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;UP&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">elif</span> <span class="n">status</span> <span class="o">==</span> <span class="s2">&#34;VPNDOWN&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td class=</span><span class="se">\&#34;</span><span class="s2">vpndown</span><span class="se">\&#34;</span><span class="s2">&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;DOWN&#34;</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="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;No Status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;No Status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">onerow</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34;&lt;/td&gt;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Once complete, we append this row to the status list</span>
</span></span><span class="line"><span class="cl">        <span class="n">firewallstatus</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">onerow</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># All done! Return our list of statuses!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">firewallstatus</span>
</span></span></code></pre></div><p>That block might not make a ton of sense at the moment - but it will shortly. Essentially I have a script that is connecting to each firewall via the SRX API and polling the VPN status for each peer. The script is then writing all of this information to a field in the database called vpnstatus in one long string, which kinda looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="err">u&#39;US-1&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;US-2&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;EU-1&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;EU-2&#39;:</span> <span class="err">&#39;VPNUP&#39;,</span> <span class="err">u&#39;CN-1&#39;:</span> <span class="err">&#39;VPNDOWN&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>The buildrows function is then just grabbing each firewall and then parsing this data to figure out what it has connections to - then generates a row in our table.</p>
<p>With that done, our index function within views.py needs a little work to start displaying our table. You&rsquo;ll also notice we&rsquo;re importing the models we created, as well as a template loader - which I&rsquo;ll get to in a moment. So here is what that function will look like when we&rsquo;re done:</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">.models</span> <span class="kn">import</span> <span class="n">Firewall</span><span class="p">,</span> <span class="n">Datacenter</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.template</span> <span class="kn">import</span> <span class="n">loader</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">ast</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">datacenter_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_status</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This will grab each datacenter object (if they&#39;re marked active) from the </span>
</span></span><span class="line"><span class="cl">    <span class="c1"># database and add them to a list</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="n">Datacenter</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;datacenter_code&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">each</span><span class="o">.</span><span class="n">datacenter_active</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="n">datacenter_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Same thing here - grab our firewalls and append to a list</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">each</span> <span class="ow">in</span> <span class="n">Firewall</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">order_by</span><span class="p">(</span><span class="s1">&#39;firewall_name&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">each</span><span class="o">.</span><span class="n">firewall_active</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="n">firewall_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">each</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Now we pass those lists to the buildrows function to assemble our HTML table</span>
</span></span><span class="line"><span class="cl">    <span class="n">firewall_status</span> <span class="o">=</span> <span class="n">buildrows</span><span class="p">(</span><span class="n">firewall_list</span><span class="p">,</span> <span class="n">datacenter_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re also going to apply a template to make the page look fancy</span>
</span></span><span class="line"><span class="cl">    <span class="n">template</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="s1">&#39;web/index.html&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># This is taking all of our lists, and passing them into our HTML template</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># so that it can build the page semi-automatically</span>
</span></span><span class="line"><span class="cl">    <span class="n">context</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;datacenter_list&#39;</span><span class="p">:</span><span class="n">datacenter_list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;firewall_list&#39;</span><span class="p">:</span> <span class="n">firewall_list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;firewall_status&#39;</span><span class="p">:</span><span class="n">firewall_status</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="n">request</span><span class="p">))</span>
</span></span></code></pre></div><p>Alright - our views.py file is now complete. I wanted to keep the index function kind of simple, so we just have it pull data from the database, pass it to another function for processing, then return it to the browser for display.</p>
<p>So in the code above, you might have noticed that I was using an HTML template to make the page look fancier. I did this by going out to Google and finding a free CSS template for HTML tables. I won&rsquo;t post the CSS here, but there are plenty of free templates out there - just find whatever suits your taste. Once you have that CSS file, create a directory (within our app folder) called static and place the CSS file in there.</p>
<p>Then, it&rsquo;s one simple template HTML file to load our CSS and render our table. Within the app directory, I created a templates folder, then a folder called web within that. I added a new file called index.html - which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>SRX VPN Dashboard<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">align</span><span class="o">=</span><span class="s">&#34;center&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% load static %}
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Load the CSS template here --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/css&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;{% static &#39;style.css&#39; %}&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Add a header to the table --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;table-title&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>SRX VPN Dashboard<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Create our first row, which will be a list of each data center location --&gt;</span>
</span></span><span class="line"><span class="cl">    {% if firewall_list %}
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">table</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;container&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {% for datacenter in datacenter_list %}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;</span>{{ datacenter.datacenter_code }}<span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {% endfor %}
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- Then we add a row for each firewall, followed by its status 
</span></span></span><span class="line"><span class="cl"><span class="c">             for each datacenter --&gt;</span>
</span></span><span class="line"><span class="cl">        {% for firewall in firewall_status %}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            {{firewall|safe}}
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        {%endfor%}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- If something goes wrong, we just print an error --&gt;</span>
</span></span><span class="line"><span class="cl">    {% else %}
</span></span><span class="line"><span class="cl">        No firewalls were found.
</span></span><span class="line"><span class="cl">    {% endif %}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Guess what? The dashboard is complete! Now we can run our Django server using &lsquo;./manage.py runserver&rsquo; and take a look at what we have created.</p>
<p><img alt="image" loading="lazy" src="/content/images/2021/01/dashboard-nodata.png#center"></p>
<p>Pretty simplistic - but we&rsquo;re getting very close to what I wanted to accomplish. The dashboard loads and generates the table, but it doesn&rsquo;t have any data yet.</p>
<p>In the next post - We&rsquo;ll take a look at building the backend script to poll all of our SRX firewalls for VPN connections. I hope you enjoyed reading this - let me know what you think in the comments below!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 2 – Learning Django)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/</link>
      <pubDate>Tue, 13 Jun 2017 08:00:18 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/</guid>
      <description>In order to build my Juniper SRX VPN dashboard, I opted to use Django. Let&amp;rsquo;s take a quick look at how I got started.</description>
      <content:encoded><![CDATA[<p>This is a multi-part series - If you just hit this page, please check out the prior post first!</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
</ul>
<hr>
<p>Once I figured out the overall idea behind what I wanted to do - the next big part was figuring out where to start. As I mentioned in part 1, I had never used Django before starting this project. So I figured that learning how to to use a new web framework might be the best place to begin.</p>
<p>The great thing, is that Django already has some great tutorials on how to get the packages installed and begin working on your first project. Their tutorials can be found here:</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/1.11/intro/install/">Installing Django</a></li>
<li><a href="https://docs.djangoproject.com/en/1.11/intro/tutorial01/">Writing your first app</a> - Credit to this page for some parts of the examples below</li>
</ul>
<p>I used both of these tutorials when I wrote my dashboard, and pretty much just modified what they were doing to fit my needs. After I got a baseline down, it was pretty easy to keep going and complete what I wanted to do.</p>
<p><strong>Note:</strong> Just as a pure warning - I&rsquo;m a network admin, not a professional programmer. Python, Django, and the Junos PyEZ libraries make it easy enough for me to make my ideas into a reality. However, that doesn&rsquo;t mean I write perfect or even great code. (I learned Python from <a href="https://learnpythonthehardway.org/book/">Learn Python The Hard Way</a> - I highly recommend this site, as it was a great resource!)</p>
<p>Alright, with that out of the way - Let&rsquo;s get started!</p>
<h2 id="step-one---installing-django">Step One - Installing Django</h2>
<p>First thing is first, I learned Python on the 2.7.x chain, and I&rsquo;ve been terrible about forcing myself to switch to 3.x. So for the purposes of this tutorial, everything I&rsquo;ve written was intended for Python 2.7.5.
Installing the actual Django package is a super simple task (if you use <a href="https://pip.pypa.io/en/latest/installing/#installing-with-get-pip-py">pip</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos ~]# pip install django
</span></span><span class="line"><span class="cl">Collecting django
</span></span><span class="line"><span class="cl"> Downloading Django-1.11.2-py2.py3-none-any.whl (6.9MB)
</span></span><span class="line"><span class="cl"> 100% |████████████████████████████████| 7.0MB 140kB/s
</span></span><span class="line"><span class="cl">Collecting pytz (from django)
</span></span><span class="line"><span class="cl"> Downloading pytz-2017.2-py2.py3-none-any.whl (484kB)
</span></span><span class="line"><span class="cl"> 100% |████████████████████████████████| 491kB 1.9MB/s
</span></span><span class="line"><span class="cl">Installing collected packages: pytz, django
</span></span><span class="line"><span class="cl">Successfully installed django-1.11.2 pytz-2017.2
</span></span></code></pre></div><h2 id="step-two---get-junos-pyez-libraries">Step Two - Get JunOS pyEZ libraries</h2>
<p>I already wrote about this earlier in <a href="/getting-started-with-junos-pyez">Getting Started with JunOS PyEZ</a>. If you don&rsquo;t already have the JunOS packages installed, check out that page for the instructions.</p>
<h2 id="step-three---create-the-project">Step Three - Create the Project</h2>
<p>Django is pretty wonderful from my brief experiences with it. They package a number of tools that make starting a new project very simple.</p>
<p>So to start off with our project, we&rsquo;re going to use the django-admin tool:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos ~]# django-admin startproject junos-dashboard
</span></span></code></pre></div><p>This will create a base directory structure for us to begin our project. We&rsquo;ll dive into what these files and folders are as we touch them - but for now, you&rsquo;ll need to make one minor edit to ~/junos-dashboard/junos-dashboard/settings.py. Open that file up in your favorite text editor, and find the ALLOWED_HOSTS setting. Add in the IP address of your linux VM, like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ALLOWED_HOSTS = [&#39;10.2.32.90&#39;]
</span></span></code></pre></div><p>Once that&rsquo;s done, go to the root of your project (~/junos-dashboard/) and test out the web server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# ./manage.py runserver 10.2.32.90:8000
</span></span></code></pre></div><p>Django includes it&rsquo;s own web server for development and testing, which is extremely helpful. Once you run that command, try to hit your page in a browser and just make sure it loads.</p>
<p>Once that&rsquo;s done, make sure you&rsquo;re in the root of your project - and we will create our dashboard application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[root@0x2142-Centos junos-dashboard]# django-admin startapp vpn
</span></span></code></pre></div><p>This command just creates <em>another</em> directory structure within your project. I think this is helpful though, because then I don&rsquo;t have to worry about which files I need to create or how they should be laid out - Django just handles that for us.</p>
<h2 id="step-four---getting-our-web-server-to-return-our-app-page">Step Four - Getting our web server to return our app page</h2>
<p>Okay - So next we need to make a couple of minor edits in a few files. Within our app directory, there are two files (views.py and urls.py) that handle the main parts of our web interface. Note - urls.py doesn&rsquo;t exist by default and you will have to create it!</p>
<p>Within views.py, we&rsquo;re going to create our dashboard homepage with a quick test 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="c1"># Import stuff</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.template</span> <span class="kn">import</span> <span class="n">loader</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">ast</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># This is our index page</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Return some plain text - just so we know it worked!</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="s2">&#34;This page will eventually be a magical dashboard!&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>This file is where we will eventually be putting in all the code for our dashboard page. When we hit our webserver, this file is executed to generate the view that we&rsquo;ll see.</p>
<p>Next, we need to create a urls.py file within the same folder as views.py. This file is just a way to direct traffic. If we receive traffic to a regex of ^$, which is no path, then we are going to redirect it to the index function in views.py:</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"># Import stuff</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">views</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#Redirect to index function in views.py</span>
</span></span><span class="line"><span class="cl"><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^$&#39;</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">index</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">&#39;index&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Finally, we&rsquo;re going to grab the urls.py file in our <em>project</em> folder - not the one we just edited within the app. So this should be ~/junos-dashboard/junos-dashboard/urls.py. In this file, we just need to tell the web server to include the urls.py file we created when evaluating traffic. This file should already exist and have content in it for routing traffic to the admin page, so just add an entry under urlpatterns - should look like this:</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">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^$&#39;</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">&#39;vpn.urls&#39;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl"> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^admin/&#39;</span><span class="p">,</span> <span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>I just want to point out that in this case, we&rsquo;re catching traffic again with the ^$ regex. This is so that any traffic to the main page (in this case <a href="http://10.2.32.90:8000">http://10.2.32.90:8000</a>) is automatically sent to our vpn app. However, if you had multiple apps running, you might want to give each one it&rsquo;s own directory.</p>
<p>Go ahead and execute the runserver tool again, and validate that you&rsquo;re able to successfully hit the page and retrieve the test text we put in.</p>
<hr>
<p>Okay, I think we&rsquo;re going to stop here for this post. At this point we have Django installed and our project/app created. We also have the beginnings of our dashboard page. In the next post, we&rsquo;ll actually start building out the dashboard interface!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/">Part 1 - Initial Thoughts</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Building a VPN Dashboard using Django and JunOS pyEZ (Part 1 - Initial thoughts)</title>
      <link>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/</link>
      <pubDate>Wed, 07 Jun 2017 08:32:04 +0000</pubDate>
      <guid>https://0x2142.com/building-a-vpn-dashboard-using-django-and-junos-pyez-part-1-initial-thoughts/</guid>
      <description>I decided to build a web GUI to monitor Juniper SRX VPN tunnels. Here&amp;rsquo;s what I was thinking&amp;hellip;</description>
      <content:encoded><![CDATA[<p>So maybe you&rsquo;re like me - you&rsquo;ve done a bit of everything in the past, but now you&rsquo;ve specialized on something (like networking). Know what the best part of that is? Using all that knowledge and experience to make things happen. In this case, I&rsquo;m talking about some of my prior experiences in scripting in automation, paired with some pretty great network APIs.</p>
<p>One thing my current job has never had is a good way to view VPN tunnel status between our firewalls. Our Check Point firewalls don&rsquo;t really provide a good high-level view, which has unfortunately caused some confusion around whether or not a site-to-site VPN tunnel is currently established. Luckily, over the past year I&rsquo;ve had the opportunity to install over 20 new Juniper SRX firewalls - mostly made up of SRX 1500 and SRX 345 models. I&rsquo;ve written a bit before about the JunOS APIs and pyEZ (their Python library), but I&rsquo;m always really excited at a new use case for network automation.</p>
<p>So recently I decided that it would be great to build a web-based dashboard, which would query all of our SRX firewalls for currently connected VPN tunnels. Approximately 90% of the current tunnels are site-to-site between our own data center locations, with the other 10% being external to customers. My idea was that I would have a simple HTML table, which would show each data center along the top and bottom and whether or not each was connected to each other, kinda like this:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><strong>Location 1</strong></th>
          <th><strong>Location 2</strong></th>
          <th><strong>Location 3</strong></th>
          <th><strong>Location 4</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Location 1</strong></td>
          <td>N/A</td>
          <td>UP</td>
          <td>UP</td>
          <td>UP</td>
      </tr>
      <tr>
          <td><strong>Location 2</strong></td>
          <td>UP</td>
          <td>N/A</td>
          <td>UP</td>
          <td>DOWN!</td>
      </tr>
      <tr>
          <td><strong>Location 3</strong></td>
          <td>UP</td>
          <td>UP</td>
          <td>N/A</td>
          <td>UP</td>
      </tr>
      <tr>
          <td><strong>Location 4</strong></td>
          <td>UP</td>
          <td>DOWN!</td>
          <td>UP</td>
          <td>N/A</td>
      </tr>
  </tbody>
</table>
<p>I wasn&rsquo;t really concerned about making it look fancy - just dynamically updatable by whatever backend mechanism I used. Speaking of which, I had also assumed I would just be writing some simple Python script to pull VPN info from each device, then just write it to an HTML file. I haven&rsquo;t really done much web stuff with Python in the past, so I set out to learn a bit and figure out what the best approach might be.</p>
<p>I ended up settling on trying out <a href="https://www.djangoproject.com">Django</a> to write the frontend web stuff. I had never used it before, but I&rsquo;ve been interested in trying - and what&rsquo;s a better time to learn, than when you have something that needs to be accomplished? One thing that really pushed me towards Django for this project was the built-in administration page. This was huge for me, because it meant that I could easily have a way for other people to update the web dashboard. Whenever a new location came online, I wouldn&rsquo;t have to go update the script directly - anyone on my team could log into the admin page and make the changes.</p>
<p>In this post I just wanted to get through what my ideas were behind this project. In the next few weeks, I&rsquo;ll begin explaining how I built the dashboard using Django and use pyEZ to scrape VPN status from each firewall.</p>
<p>Thoughts? Drop a comment below!</p>
<hr>
<p>This is a multi-part series - Check out the other posts:</p>
<ul>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-2-learning-django/">Part 2 - Leaning Django</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-3-creating-the-dashboard/">Part 3 - Creating the Dashboard</a></li>
<li><a href="/building-a-vpn-dashboard-using-django-and-junos-pyez-part-4-polling-the-srx/">Part 4 - Polling the SRX</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Started with JunOS PyEZ</title>
      <link>https://0x2142.com/getting-started-with-junos-pyez/</link>
      <pubDate>Tue, 24 Jan 2017 08:00:20 +0000</pubDate>
      <guid>https://0x2142.com/getting-started-with-junos-pyez/</guid>
      <description>Quickly get started using Juniper&amp;rsquo;s Python SDK &amp;amp; interact with JunOS devices</description>
      <content:encoded><![CDATA[<p><sup>Note: I may receive commissions for purchases made through links in this post. This is to help support my blog and does not have any impact on my recommendations.</sup></p>
<hr>
<blockquote>
<p>This guide is written for CentOS 7. If you&rsquo;re running another distro, find your dependencies <a href="https://www.juniper.net/techpubs/en_US/junos-pyez1.0/topics/task/installation/junos-pyez-server-installing.html">here</a>.</p></blockquote>
<p>Last year we had to begin migrating off of some of our older Juniper SSG firewalls since we were beginning to push them to their throughput limits. We evaluated a couple of vendors but ultimately decided to stay with Juniper and purchase SRX 1500 firewalls, which are capable of up to 10G throughput. After a while of working with these firewalls, I have to say they&rsquo;re pretty solid devices and I&rsquo;m extremely happy with them. One of the main reasons I like them so much is the ease of automation, which is what we&rsquo;re going to dive into today. If you need a device for lab/test - Amazon has the <a href="https://www.amazon.com/gp/product/B01ICEO2U4/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01ICEO2U4&amp;linkId=35fbe8300af4e5d1e26e7a860782b3ca">SRX 300</a> for less than $300. I&rsquo;ll likely be picking one up in the near future for easier automation testing.</p>
<p>Juniper provides an awesome library for SRX management called PyEZ. Here is what we need to get the toolkit ready to use in our scripts:</p>
<h2 id="install-dependencies">Install dependencies</h2>
<p>I run CentOS here, so I just needed to grab the following packages:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">yum -y install python-devel libxml2-devel libxslt-devel gcc openssl openssl-devel libffi-devel
</span></span></code></pre></div><p>Just a quick note - Juniper&rsquo;s web page doesn&rsquo;t actually mention openssl-devel, but their tools will fail to install without it</p>
<h2 id="get-pip">Get pip</h2>
<p>If you don&rsquo;t have it already, then download the pip installer <a href="https://bootstrap.pypa.io/get-pip.py">here</a>. Then just run the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python get-pip.py
</span></span></code></pre></div><h2 id="install-pyez">Install PyEZ</h2>
<p>Once everything else is set, this is the easy part:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pip install junos-eznc
</span></span></code></pre></div><p>After all that is done, we can get to the exciting part: automating something so you don&rsquo;t have to do it anymore! So here is what we&rsquo;re going to throw in our script just to get started:</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"># Import the JunOS modules we need</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">jnpr.junos</span> <span class="kn">import</span> <span class="n">Device</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define the login credentials and address for the target firewall</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Need to provide device address, user account, and password</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Note: Juniper also provides authentication via key-pair, which would be more secure than username/password</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">srx</span> <span class="o">=</span> <span class="n">Device</span><span class="p">(</span><span class="s1">&#39;10.10.10.10&#39;</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s1">&#39;deviceuser&#39;</span><span class="p">,</span> <span class="k">pass</span><span class="o">=</span><span class="s1">&#39;devicepass&#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"># Open the connection</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">srx</span><span class="o">.</span><span class="n">open</span><span class="p">()</span>
</span></span></code></pre></div><p>Once you have that going, it&rsquo;s pretty easy to start making calls to collect data or change the configuration. In one of the first projects I used this for, I was making use of the API to reset VPN tunnels. This was due to an issue with a cross-vendor tunnel, which would occasionally break during VPN re-keys and only negotiate a uni-directional tunnel. So the script was written to detect a uni-directional flow of traffic, then log into the SRX and reset the VPN - which would force a renegotiation and fix the issue.</p>
<p>So in order to accomplish the SRX-side of that script, I used the following to reset the IKE and IPSec security associations:</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"># Clear IKE security association for a given peer</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">srx</span><span class="o">.</span><span class="n">rpc</span><span class="o">.</span><span class="n">clear_ike_security_association</span><span class="p">(</span><span class="n">peer_address</span><span class="o">=</span><span class="s1">&#39;20.20.20.20&#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"># Check to see if command was accepted</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">response</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="nb">print</span> <span class="s2">&#34;IKE SA Cleared&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Clear IPSec security association for a given peer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Note: In this case, you need to provide the index ID</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Get the index ID from running &#39;*show security ipsec sa&#39; *on the SRX</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">srx</span><span class="o">.</span><span class="n">rpc</span><span class="o">.</span><span class="n">clear_ipsec_security_association</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="s1">&#39;123456&#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"># Same thing - Check to make sure the command succeeded</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">response</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="nb">print</span> <span class="s2">&#34;IPSec SA Cleared&#34;</span>
</span></span></code></pre></div><p>This provides a pretty basic example of how easy it is to control the SRX via a Python script. You might be wondering: This is great, but how do I find the names of those calls? Well, Juniper made that extremely simple as well! Just take any command on the SRX and pipe it through <code>display xml rpc</code> and the device will tell you exactly what you need. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">root@SRX123&gt; show security ipsec sa | display xml rpc
</span></span><span class="line"><span class="cl"><span class="nt">&lt;rpc-reply</span> <span class="na">xmlns:junos=</span><span class="s">&#34;http://xml.juniper.net/junos/15.1X49/junos&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;rpc&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;get-security-associations-information&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/get-security-associations-information&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/rpc&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;cli&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;banner&gt;</span>{primary:node0}<span class="nt">&lt;/banner&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/cli&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/rpc-reply&gt;</span>
</span></span></code></pre></div><p>In this case, we are running the command which would normally print all of the connected IPSec tunnels. So the section under <em>rpc</em> is where we want to look. Now just take the get-security-associations-information, and change the dashes to underscores. So to use this in a script we would call srx.rpc.get_security_associations_information(). I&rsquo;ve actually used this command to build a VPN status dashboard using Python and Django, which I accomplished by just pulling all the IPSec tunnels and parsing them into a web table.</p>
<p>As a final note, I would <strong>highly</strong> recommend creating a separate service account on the SRX for scripting. A separate user account with limited permissions would be the recommended way to go here. It might seem like a pain to set up, but it&rsquo;s worth it in terms of security. Here is what I have configured on my SRX firewalls for the API service account:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">system</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="err">login</span> <span class="err">{</span>
</span></span><span class="line"><span class="cl">        <span class="err">class</span> <span class="err">api-class</span> <span class="err">{</span>
</span></span><span class="line"><span class="cl">            <span class="err">permissions</span> <span class="err">security-control;</span>
</span></span><span class="line"><span class="cl">            <span class="err">allow-commands</span> <span class="nt">&#34;(clear security ike)|(clear security ipsec)&#34;</span><span class="err">;</span>
</span></span><span class="line"><span class="cl">            <span class="err">deny-commands</span> <span class="s2">&#34;(clear)|(file)|(file show)|(help)|(load)|(monitor)|(op)|(request)|(save)|(set)|(start)|(test)&#34;</span><span class="err">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="err">user</span> <span class="err">apiuser</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="err">full-name</span> <span class="err">API_Service_Acct;</span>
</span></span><span class="line"><span class="cl">            <span class="err">uid</span> <span class="err">125;</span>
</span></span><span class="line"><span class="cl">            <span class="err">class</span> <span class="err">api-class;</span>
</span></span><span class="line"><span class="cl">            <span class="err">authentication</span> <span class="err">{</span>
</span></span><span class="line"><span class="cl">                <span class="err">encrypted-password</span> <span class="err">*&lt;encrypted</span> <span class="err">string</span> <span class="err">here&gt;*</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="err">}</span>
</span></span><span class="line"><span class="cl">    <span class="err">}</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>So using the above example, you should only define the necessary commands in the <em>allow-commands</em> section. If the account is ever compromised, we are severely limiting the amount of damage that could be done. A little extra effort, but potentially a big payoff.</p>
<p>So what did you think of this tutorial? Helpful? Have questions? Let me know in the comments below!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
