<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Shaun</title>
    <description>The latest articles on Forem by Shaun (@badgerbadgerbadgerbadger).</description>
    <link>https://forem.com/badgerbadgerbadgerbadger</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F37592%2F08d2cdd5-4d76-4c2c-bbde-159275becd39.jpg</url>
      <title>Forem: Shaun</title>
      <link>https://forem.com/badgerbadgerbadgerbadger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/badgerbadgerbadgerbadger"/>
    <language>en</language>
    <item>
      <title>Setting up DSMR Meter Readings via a Raspberry Pi</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Thu, 23 May 2024 12:40:23 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/setting-up-dsmr-meter-readings-via-a-raspberry-pi-4idl</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/setting-up-dsmr-meter-readings-via-a-raspberry-pi-4idl</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://badgerbadgerbadgerbadger.dev/posts/automation/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/" rel="noopener noreferrer"&gt;https://badgerbadgerbadgerbadger.dev/posts/automation/2024-05-23-setting-up-dsmr-readings-via-raspberry-pi/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The internet is probably full of tutorials on how to do this right, but I had to struggle a bit to figure it out for myself. My Pi randomly crashed, the other day, and I'm having to do some of the setup again and having done this for the first time months ago (and having now mostly forgotten what I did), I decided I should blog about it as I retread my steps and recreate my setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aside&lt;/strong&gt;: The &lt;a href="https://en.wikipedia.org/wiki/Unix_shell" rel="noopener noreferrer"&gt;internet&lt;/a&gt; tells me that the C shell was the first to introduce the &lt;code&gt;history&lt;/code&gt; command. It would not be hyperbolic to say that without &lt;code&gt;history&lt;/code&gt; my task of recreating my setup would be significantly more difficult. I used it extensively to figure out what I ran when I needed to do this all those months ago. I should probably containerise or chefise or ansibilise or something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I live in a country that uses &lt;a href="https://www.domoticz.com/wiki/Dutch_DSMR_smart_meter_with_P1_port" rel="noopener noreferrer"&gt;DSMR&lt;/a&gt; meters and have one in my home. My girlfriend wanted a closer look at our electricity consumption, so I decided to hook up a raspberry Pi to the meter's &lt;a href="https://www.fluvius.be/sites/fluvius/files/2020-03/1901-fluvius-technical-specification-user-ports-digital-meter.pdf" rel="noopener noreferrer"&gt;P1&lt;/a&gt; port and send those readings to my home lab server using the Pi as a relay.&lt;/p&gt;

&lt;p&gt;I bought a RJ12 --&amp;gt; USB cable off of Amazon and tried to read from my Pi's USB port by using first &lt;a href="https://linux.die.net/man/1/cu" rel="noopener noreferrer"&gt;&lt;code&gt;cu&lt;/code&gt;&lt;/a&gt; and then &lt;a href="https://pythonhosted.org/pyserial/index.html" rel="noopener noreferrer"&gt;pyserial&lt;/a&gt; (which is a much friendlier interface).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using cu&lt;/span&gt;
cu &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/ttyUSB0 &lt;span class="nt"&gt;-s&lt;/span&gt; 115200 &lt;span class="nt"&gt;--parity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;none

&lt;span class="c"&gt;# Using pyserial&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; serial.tools.miniterm /dev/ttyUSB0 115200 &lt;span class="nt"&gt;--xonxoff&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially neither tool gave me any readings and I nearly tore my luscious hair out before finally realising that it was a bad cable I was using. I returned it and bought a more respectable cable from &lt;a href="https://www.robbshop.nl/slimme-meter-kabel-usb-p1-1-meter" rel="noopener noreferrer"&gt;robbshop.nl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that gave me results!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FBadgerBadgerBadgerBadger%2FBadgerBadgerBadgerBadger.github.io%2Fassets%2F5138570%2Fbdc7ee3d-9f61-480a-b3ec-22e04f5e558e" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FBadgerBadgerBadgerBadger%2FBadgerBadgerBadgerBadger.github.io%2Fassets%2F5138570%2Fbdc7ee3d-9f61-480a-b3ec-22e04f5e558e" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had a few different ways of turning the meter readings into useful insights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run HomeAssistant on my Pi with direct access to the port&lt;/strong&gt;: Not an option since my HA instance was already running on my home lab server. The Pi was meant to be a relay only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DSMR to MQTT&lt;/strong&gt;: I could run something that would read the DSMR telegrams and send them to an MQTT broker to which I could subscribe my HA integration. Definitely an option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serial to Network Proxy&lt;/strong&gt;: The option I ended up going with since it felt the simplest to me. I would run a program that would expose the DSMR telegrams over a TCP connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The program I landed on for doing this is &lt;a href="https://ser2net.sourceforge.net/" rel="noopener noreferrer"&gt;ser2net&lt;/a&gt; With a little help from ChatGPT I figured out this &lt;a href="https://github.com/chargebyte/ser2net/blob/master/ser2net.conf" rel="noopener noreferrer"&gt;ser2net config&lt;/a&gt;. Comments describe what the various fields do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# defines an alias confver, I'm not entirely sure if this does anything&lt;/span&gt;
&lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;confver&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;

&lt;span class="c1"&gt;# begins a new connection definition and assigns it an alias `con00`.&lt;/span&gt;
&lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;con00&lt;/span&gt;
  &lt;span class="c1"&gt;# specifies how incoming network connections will be accepted, in this case the Telnet protocol with RFC 2217 support (I no clue what that means), and the connection will be over TCP and listen on port 2013c (this part I understand)&lt;/span&gt;
  &lt;span class="na"&gt;accepter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telnet(rfc2217),tcp,2013&lt;/span&gt;
  &lt;span class="c1"&gt;# specifies how the serial device will be connected, includes path to the serial device file and port settings, and it is to be configured in local mode (not sure what local mode means)&lt;/span&gt;
  &lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serialdev,/dev/ttyUSB0,115200n81,local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I paired this with HA's &lt;a href="https://www.home-assistant.io/integrations/dsmr" rel="noopener noreferrer"&gt;DSMR Slimme Meter&lt;/a&gt; integration with it set to listen on the network, pointed at my Pi on port 2013.&lt;/p&gt;

&lt;p&gt;And we have lift-off!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FBadgerBadgerBadgerBadger%2FBadgerBadgerBadgerBadger.github.io%2Fassets%2F5138570%2Ff7a28b8c-afc3-4119-bbca-8f468227f24f" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FBadgerBadgerBadgerBadger%2FBadgerBadgerBadgerBadger.github.io%2Fassets%2F5138570%2Ff7a28b8c-afc3-4119-bbca-8f468227f24f" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Solar readings are coming in via a different integrations and configured together with the DSMR readings using HomeAssistant's Energy Dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>dsmr</category>
      <category>homeassistant</category>
      <category>electricity</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>Is it Dry Yet?</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Fri, 12 Apr 2024 08:17:30 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/is-it-dry-yet-2fe5</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/is-it-dry-yet-2fe5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clarification&lt;/strong&gt;: This is literally about my laundry. I am not being metaphorical. I am not talking about my feelings or my secrets. I am talking about my clothes.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Our washing machine is a Samsung something-something that sends me a notification on the &lt;a href="https://www.samsung.com/be/smartthings/app/"&gt;SmartThings app&lt;/a&gt; when it completes a wash cycle. This is useful since we almost always need to follow up a washing cycle with a drying one and maybe even another load of laundry. The machine isn't a combined-washer-dryer so someone has to move the wet clothes from our smart washer to our dumb dryer.&lt;/p&gt;

&lt;p&gt;The fact that our dryer is "dumb" is what led me to my automation journey. If it was Wifi-enabled and also sending out notifications as it completed a cycle, I wouldn't be writing this post. &lt;/p&gt;

&lt;p&gt;Having been spoiled by knowing exactly when my washing has completed (so I know when to go move things to the dryer) I decided to do something similar for the dryer. This is especially useful, for example, when the clothes need an extra drying cycle or when there are more wet clothes that are waiting in the washer. In these times it is useful to know if the dryer has finished a cycle.&lt;/p&gt;

&lt;p&gt;And since it is not a smart device, I have to add the brains, somehow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan of Attack
&lt;/h2&gt;

&lt;p&gt;This is how I decided to approach the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have a &lt;a href="https://www.shelly.com/en-be/products/shop/shelly-plus-plug-s"&gt;Shelly smart plug&lt;/a&gt; that I would put between Drew (the dryer is called Drew) and the wall.&lt;/li&gt;
&lt;li&gt;The plug would transmit power readings to my &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt; setup.&lt;/li&gt;
&lt;li&gt;I would do &lt;em&gt;something&lt;/em&gt; to detect when the power reading goes down from some high number to some very low number (I'm assuming it never goes to 0 since the little LED panel needs power).&lt;/li&gt;
&lt;li&gt;I will notify myself of this &lt;em&gt;somehow&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the &lt;em&gt;something&lt;/em&gt; and &lt;em&gt;somehow&lt;/em&gt; parts I was unsure of. My Home Assistant setup is as basic as it gets. I only started my home automation journey a month or two ago and was unfamiliar with the possibilities. However, this felt like something that should be within the realm of HASS's capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovering Automations
&lt;/h2&gt;

&lt;p&gt;The first thing I discovered as I dug further into Home Assistant was &lt;a href="https://www.home-assistant.io/docs/automation/"&gt;Automation&lt;/a&gt;s. In Home Assistant you create automations via the Automations system. You can turn on a light when a motion sensor senses someone in the room; you can turn on the vent when humidity reaches a certain level; you can make all the lights turn pink and romantic music play over the speakers when it is February 14th and your beloved comes home from work. The possibilities are &lt;em&gt;endless&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automation Setup
&lt;/h2&gt;

&lt;p&gt;Figuring out the Automation setup I wanted was straightforward. There are 3 key components to the Home Assistant Automation system.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.home-assistant.io/docs/automation/trigger/"&gt;Triggers&lt;/a&gt;: The thing that kicks off an Automation. These can be of many types ranging from a number on a sensor changing, to an incoming MQTT payload.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.home-assistant.io/docs/automation/condition/"&gt;Conditions&lt;/a&gt;: An optional extra check to ensure that when the Automation triggers everything is in the state you want it to be. You can check the value of sensors, check the on/off status of devices, or even &lt;a href="https://www.home-assistant.io/docs/scripts/conditions/#sun-condition"&gt;check if the sun is up&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.home-assistant.io/docs/automation/action/"&gt;Actions&lt;/a&gt;: This where we &lt;em&gt;do&lt;/em&gt; something in our Automation. This is where you will want to set up that electric rooster crowing when the sun comes up (if you want to build your own weird alarm clock for some reason), or send a message on a webhook when the kids are using too much electricity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thinking over my needs I decided on the following for my Trigger/Condition/Action setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: I want the Automation to start when Drew's power draw falls below 10 W (as reported by Sheila, the smart plug), and has remained under 10 W for more than 5 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: I want to ensure this Automation only fires if we have consumed more than a 100 Watts of power sometime in the last 5 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action&lt;/strong&gt;: Inform me somehow that this has happened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the Trigger and Condition were straightforward, the Action was a bit more nebulous. At first, I wasn't entirely sure how to notify myself. I looked into &lt;a href="https://www.home-assistant.io/integrations/notify.rest/"&gt;Home Assistant's Restful Notifications&lt;/a&gt; but could not make it work for the life of me. Then I decided to check if I could send an email. I found &lt;a href="https://www.home-assistant.io/integrations/smtp/"&gt;Home Assistant's SMTP Notifications&lt;/a&gt; and decided to give it a go. Again, I could not make it work. It asked me to enter my Gmail password in the Home Assistant configuration file, but I was not comfortable doing that. I decided to look for another way.&lt;/p&gt;

&lt;p&gt;I finally stumbled upon the &lt;a href="https://www.home-assistant.io/integrations/ios/"&gt;Home Assistant iOS App&lt;/a&gt; which I installed on my phone and pointed to my Home Assistant server. This allowed me to send notifications to my phone (my phone became available as a target for notifications).&lt;/p&gt;

&lt;p&gt;The full Automation setup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Is it dry yet?&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Notifies when Drew (the dryer) has finished a drying cycle.&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sheila Power Below 10 W&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;numeric_state&lt;/span&gt;
    &lt;span class="na"&gt;entity_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sensor.sheila_power&lt;/span&gt;
    &lt;span class="na"&gt;below&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;numeric_state&lt;/span&gt;
    &lt;span class="na"&gt;entity_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sensor.sheila_power_5_minutes_ago&lt;/span&gt;
    &lt;span class="na"&gt;above&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify.mobile_app_sharpie&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Drew has finished a cycle. You should unload him.&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dryer Cycle Finished&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify.mobile_app_buttercups_android&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Drew has finished a cycle. You should unload him.&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dryer cycle finished.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;media_player.play_media&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;entity_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;media_player.thuis&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;announce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;media_content_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mp3&lt;/span&gt;
      &lt;span class="na"&gt;media_content_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://&amp;lt;static-host&amp;gt;/drew-finished-drying.mp3&lt;/span&gt;
&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;notify.mobile_app_sharpie&lt;/code&gt; and &lt;code&gt;notify.mobile_app_buttercups_android&lt;/code&gt; are the entities exposed by the Home Assistant iOS and Android apps on my phone and my girlfriend's phone respectively. &lt;code&gt;media_player.thuis&lt;/code&gt; is the entity exposed by my Google Nest speaker group. Not being simply content to send a notification to my phone, I also wanted to play a sound on my Google Nest speaker group. The sound is a mp3 file that I host on a static host.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sensor.sheila_power&lt;/code&gt; is the sensor entity exposed by my smart plug and what lets me monitor the power draw of the dryer. &lt;code&gt;sensor.sheila_power_5_minutes_ago&lt;/code&gt; is a sensor that I created using the &lt;a href="https://www.home-assistant.io/integrations/sql"&gt;SQL Integration&lt;/a&gt;. I created a custom sensor to get the power draw 5 minutes ago.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;states&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;metadata_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;states_meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata_id&lt;/span&gt;
                     &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;states_meta&lt;/span&gt;
                     &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;states_meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sensor.sheila_power'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_updated_ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-5 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;last_updated_ts&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In combination this ensures that this Automation only triggers when our power draw falls below 10 W for at least 5 minutes, and we have consumed more than 100 W of power just before. This gives me a good indication that the dryer has finished a cycle and I should go unload it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposing to the World
&lt;/h2&gt;

&lt;p&gt;I should mention that my Home Assistant server was not available to the internet at that point. All integrations were over local Wi-Fi with firewall rules to prevent any external access. I did not have to expose my Home Assistant server to the internet to get notifications on my phone since they are both part of a &lt;a href="https://tailscale.com/kb/1136/tailnet"&gt;Tailnet&lt;/a&gt;. This meant that as long as I had the &lt;a href="https://tailscale.com/download/ios"&gt;Tailscale App&lt;/a&gt;  on my phone and Tailscale VPN enabled, I could access my Home Assistant server from anywhere in the world.&lt;/p&gt;

&lt;p&gt;And this would have been good enough for me, but my girlfriend also needs notifications on &lt;em&gt;her&lt;/em&gt; phone and asking her to install Tailscale and turn it on/off before she could access the notifications was a bit much.&lt;/p&gt;

&lt;p&gt;So I decided to expose my Home Assistant server to the internet.&lt;/p&gt;

&lt;p&gt;My first attempt was using Let's Encrypt and DuckDNS, following a guide (or three) similar to &lt;a href="https://www.makeuseof.com/access-home-assistant-server-remotely-duckdns-letsencrypt/"&gt;this&lt;/a&gt;. I won't go into all the details, but I will say that it was a &lt;em&gt;pain&lt;/em&gt;. Ultimately, after struggling for several days, I gave up. I gave up and decided to go back to my girlfriend, head bowed, and give her the bad news.&lt;/p&gt;

&lt;p&gt;But then I stumbled upon &lt;a href="https://tailscale.com/kb/1223/funnel"&gt;Tailscale Funnels&lt;/a&gt;. Since I was using Tailscale anyway, with a single additional invocation, I could have a secure, encrypted, tunnel to my Home Assistant server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~ tailscale funnel &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8443 &lt;span class="nt"&gt;--bg&lt;/span&gt; 8123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No extra DNS required since I can use my Tailnet's name and no extra certificates required since Tailscale handles all of that for me.&lt;/p&gt;

&lt;p&gt;The only caveat to this approach is that Tailscale does not yet support domain name handling. So I can't assign a nice domain name to my Home Assistant server. Furthermore, I only have &lt;a href="https://tailscale.com/kb/1223/funnel#limitations"&gt;3 ports available to me&lt;/a&gt;. This means that if I were to have other services on that machine that I want to expose to the world, I can at most have 2 more, and they will have to be referenced by their port numbers.&lt;/p&gt;

&lt;p&gt;This is something I will have to come back to later, but for now both me and my girlfriend have access to our Home Assistant server from anywhere in the world and are happy ducks.&lt;/p&gt;

&lt;p&gt;Of course, I limited her account to only have access to the notifications and some views. I don't want her messing with my automations and I don't want a third party poking around my smart home setup.&lt;/p&gt;

</description>
      <category>homeautomation</category>
      <category>homeassistant</category>
      <category>smartplugs</category>
    </item>
    <item>
      <title>Dealing With Errors in Go Projects</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Thu, 22 Apr 2021 10:52:06 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/dealing-with-errors-in-go-projects-2e2a</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/dealing-with-errors-in-go-projects-2e2a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://badgerbadgerbadgerbadger.dev/dealing-with-errors-in-go-projects/"&gt;https://badgerbadgerbadgerbadger.dev/dealing-with-errors-in-go-projects/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Dealing With Errors in Go Projects
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In this post I talk both about the purely technical reasons for doing certain things and some practices that have made debugging production systems easier for me. I'm still fairly new to Go and still learning so if you find any issues with my approach or have a better way to approach the problem, please leave a comment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I come from a NodeJS background and my coding habits have been heavily influenced by the paradigms most used in Node. I started coding in Go quite recently and I immediately fell in love with the language. I love a lot of things about Go, but let's not get into that right now or we'll never get to the point.&lt;/p&gt;

&lt;p&gt;Let's talk, instead, of what caused me a lot of headache and frustration: &lt;strong&gt;errors&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;I didn't know what to do with the darned things. The idiomatic way in Go is to return errors as a second value and that's what pretty much all libraries do. There is a panic-recover mechanism but I've found it to be used to recover from things like illegal memory access or nil pointer dereference (these shouldn't happen if you code properly).&lt;/p&gt;

&lt;p&gt;I was looking down the barrel of either handling and logging errors on the spot, or returning them up the chain of function calls. Neither sounded like a good idea. Why? Here's why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you handle errors on the spot and log them&lt;/strong&gt; you lose context of the steps that led to this error. You can log variables local to the function where the error occured, but you have no idea what path your program took to finally call this function and face this error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you return errors up the chain&lt;/strong&gt; and then handle them at the topmost level, you end up with almost the same thing. You have the error and you know which entry point of your function eventually led to that error, but now you've lost context of the function where the error originated.&lt;/p&gt;

&lt;p&gt;Neither of them is a good idea, especially not at 4 in the morning when your boss calls you up to debug some obscure bug made more obscure by insufficient information. You need to know where the error originated, which entry point led to this and what path it followed. And of course, the details of the error itself.&lt;/p&gt;

&lt;p&gt;To work around these issues, I had this idea of annotating the error message at each stage. So say I have the following extremely contrived code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;contrived&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"thirdparty/lib"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;HandleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ControllerCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GoodResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ControllerCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;DbCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"controller failed to blah blah blah "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;DbCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db call failed for id: "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By concatenating the errors at each step I retain context. At the top level, when I log, I can see the calls that led to the error. This is better than not having much context but is extremely tedious in practice.&lt;/p&gt;

&lt;p&gt;Then I came across &lt;a href="https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package"&gt;this excellent blog post&lt;/a&gt; by Dave Cheney. And once you've read through it (seriously, read through it before continuing), you'll see that he's implemented the same idea I had but done it a gazillion times better. So I started perusing more things he'd written. The presentation link includes most of the content of the preceding articles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dave.cheney.net/2016/04/07/constant-errors"&gt;Constant Errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully"&gt;Don’t just check errors, handle them gracefully&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dave.cheney.net/2014/12/24/inspecting-errors"&gt;Inspecting Errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://godoc.org/github.com/pkg/errors"&gt;pkg/errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dave Cheney's presentation on Error Handling &lt;a href="https://www.youtube.com/watch?v=lsBF58Q-DnY"&gt;youtube&lt;/a&gt; &lt;a href="https://dave.cheney.net/paste/gocon-spring-2016.pdf"&gt;pdf&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After reading through all that and tinkering around for a while, I came up with a set of sensible guidelines for handling errors. Some of them are taken directly from Dave Cheney's blog, some I added based on my own experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Begin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always&lt;/strong&gt; check for errors if the function returns one. I know that sounds obvious but you'd be surprised at how much code I've seen that doesn't do this.&lt;/li&gt;
&lt;li&gt;Create custom error types for the errors you &lt;em&gt;know&lt;/em&gt; you are gonna face (bad input, duplicate request, etc.). Return these, wrapped by the &lt;code&gt;errors&lt;/code&gt; package, when you face them.&lt;/li&gt;
&lt;li&gt;When dealing with errors returned from a library, wrap it with &lt;code&gt;errors.Wrap&lt;/code&gt; or &lt;code&gt;errors.Wrapf&lt;/code&gt; and return.&lt;/li&gt;
&lt;li&gt;When dealing with errors returned from your own code, if:

&lt;ul&gt;
&lt;li&gt;it requires no further context, just return it.&lt;/li&gt;
&lt;li&gt;if it requires some extra context (perhaps data points captured within that function), use &lt;code&gt;Wrap&lt;/code&gt; or &lt;code&gt;Wrapf&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Handle errors without further propagation at the entry point of your program (commands in a cli or handlers in a web server). Unwrap it with &lt;code&gt;errors.Cause&lt;/code&gt; and test it:

&lt;ul&gt;
&lt;li&gt; If it is of the custom type you have defined for your project, handle accordingly (you might wanna log these at level Error or Warning since these were expected). If it's an endpoint facing your users, you might wanna have a contract with the client on what codes to return and act accordingly (you might even end up returing a generic error message). If it is an internal endpoint (thinking microservices), you could return an appropriate status code and message while logging the same.&lt;/li&gt;
&lt;li&gt; If it is of an unknown type, you might wanna log a fatal. This is an error that should never have happened. After that it's up to you.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use these guidelines to make sure I have enough information during debugging to figure what's going wrong. I still have to do &lt;code&gt;error != nil&lt;/code&gt; everywhere but I think that's not as bad a thing as I first thought. Unlike exception propagation, this way of doing things makes me stop and think at each step if I wanna let the error propagate on its own or add more context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lastly
&lt;/h2&gt;

&lt;p&gt;This approach works for me, for now. Is there a better way to do this? I don't know. There's still a lot for me to learn and I'm hoping that as I keep coding, I'll face enough new challenges to keep me reaching for better ways to code.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Bookmarklets Made My Life Easier</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Thu, 15 Apr 2021 09:48:40 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/bookmarklets-made-my-life-easier-587p</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/bookmarklets-made-my-life-easier-587p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://badgerbadgerbadgerbadger.dev/bookmarklets-made-my-life-easier/"&gt;https://badgerbadgerbadgerbadger.dev/bookmarklets-made-my-life-easier/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;

&lt;p&gt;I've put the bookmarklets that I mostly frequently use in every day life on &lt;a href="https://github.com/scionofbytes/bookmarklets"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being a Developer is Fun
&lt;/h2&gt;

&lt;p&gt;One of the advantages of browsing the web as a web-developer vs. as a layman is the fact that you get to do some pretty cool stuff to make your life easier.&lt;/p&gt;

&lt;p&gt;For example, I remember once being on a website where a certain action kept failing with no visible errors. I’d click a button and nothing would happen. It was frustrating and a layman would have been forced to quit at that point or write in to the website’s maintainers. &lt;/p&gt;

&lt;p&gt;And that's what I did too. I wrote in to the site's maintainers.&lt;/p&gt;

&lt;p&gt;But before I did that I fired up the &lt;a href="https://developers.google.com/web/tools/chrome-devtools/console/"&gt;developer console on Chrome&lt;/a&gt; and started debugging the issue. It turned out to be an error in the script that was trying to handle the button's click event. The event registered to the handler had a typo in the name.&lt;/p&gt;

&lt;p&gt;Since I could fix the code on the client, I did so and moved on with my life. I never returned to the site and the developers never wrote back. I hope they found the bug-report useful and it helped better their site.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where do bookmarklets come in?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the things I take advantage of most are &lt;a href="http://www.bookmarklets.com/"&gt;bookmarklets&lt;/a&gt;. These are tiny pieces of javascript code that run in the context of whatever page you’re on and have full access to the page’s DOM. They live in the form of a clickable bookmark on your toolbar and do their thing at the click of a mouse button. You can even configure them to ask for input before taking actions making them incredibly versatile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So how have you taken advantage of bookmarklets?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Running Queries on Rockmongo.
&lt;/h3&gt;

&lt;p&gt;Anyone who has used the tool &lt;a href="http://rockmongo.com/"&gt;Rockmongo&lt;/a&gt; knows how irritating it can be to click through multiple pages to get to a collection’s query interface and then run the query. But once I realized that this action could be represented as a query string, I quickly scripted a little querybuilder that would prompt me for an &lt;a href="https://docs.mongodb.com/manual/core/document/#document-id-field"&gt;_id&lt;/a&gt; and would perform a lookup against the relevant collection. This improved my productivity a &lt;em&gt;lot&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Show me what it looks like.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the code for one of the Rockmongo query builders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getDocumentById&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://&amp;lt;host&amp;gt;/rockmongo/index.php?db=&amp;lt;db_name&amp;gt;&amp;amp;collection=&amp;lt;CollectionName&amp;gt;&amp;amp;action=collection.index&amp;amp;format=json&amp;amp;criteria=%7B%0D%0A%09_id%3A+ObjectId%28%27&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%27%29%0D%0A%7D&amp;amp;newobj=%7B%0D%0A%09%27%24set%27%3A+%7B%0D%0A%09%09%2F%2Fyour+attributes%0D%0A%09%7D%0D%0A%7D&amp;amp;field%5B%5D=_id&amp;amp;order%5B%5D=desc&amp;amp;field%5B%5D=&amp;amp;order%5B%5D=asc&amp;amp;field%5B%5D=&amp;amp;order%5B%5D=asc&amp;amp;field%5B%5D=&amp;amp;order%5B%5D=asc&amp;amp;limit=0&amp;amp;pagesize=10&amp;amp;command=findAll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks quite messy, unfortunately, but it gets the job done. Just replace the stuff in angle brackets with your own values and this should be up and running (if it isn't, &lt;a href="//mailto:shuvophoenix@gmail.com"&gt;let me know&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Email Records on the Mandrill Dashboard
&lt;/h3&gt;

&lt;p&gt;Another example would be of querying in &lt;a href="http://www.mandrill.com/"&gt;Mandrill&lt;/a&gt;. If you've used &lt;a href="https://mailchimp.com/"&gt;Mailchimp&lt;/a&gt;'s Mandrill service, you'll know just how frustrating it can be to log in to the Mandrill dashboard and query for certain tags on certain dates. The interface is clunky and slow and you get logged out often for no reason.&lt;/p&gt;

&lt;p&gt;The only way I could maintain my sanity was to use a handy little bookmarklet to quickly run the queries that I most regularly accessed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getTodaysTag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getToday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mandrillapp.com/activity?date_format=mm%2Fdd%2Fyy&amp;amp;q=&amp;amp;date_range=custom&amp;amp;start_date=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nb"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getToday&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;stop_date=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="nb"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getToday&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;tag=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-tag&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;sender=&amp;amp;search-select-q=&amp;amp;api_key=&amp;amp;__csrf_token=&amp;lt;your-token&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above script builds a query string that would fetch for you all the emails tagged with a particular tag (which you can also accept dynamically by calling &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt"&gt;&lt;code&gt;prompt()&lt;/code&gt;&lt;/a&gt;), that were sent on the current date. You don't have to click through multiple boxes, you just click on one little bookmarklet.&lt;/p&gt;

&lt;p&gt;One caveat is that these two bookmarklets work by building querystrings which are then accessed like any normal page. This only works if you're already logged in to Rockmongo or Mandrill. If you're not, you'll be redirected to the login page, after which you'll come to the dashboard's standard homepage and will have to click on the bookmarklet again. Still better than the alternative.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That sounds pretty cool but what else can I do? I don't use Mandrill or Rockmongo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are just two examples of what you can do with bookmarklets and that's not the end of it. Tumblr's &lt;a href="https://www.tumblr.com/docs/en/lesser_known_features"&gt;Post to Tumblr&lt;/a&gt; bookmarklet let's you quickly share the contents of any page to your tumblr blog.&lt;/p&gt;

&lt;p&gt;Similarly many other services use bookmarklets to for sharing content or simply providing better readability. &lt;a href="//www.hongkiat.com"&gt;Hongkiat&lt;/a&gt; has this &lt;a href="http://www.hongkiat.com/blog/100-useful-bookmarklets-for-better-productivity-ultimate-list/"&gt;beautiful list of bookmarklets&lt;/a&gt; that you can copy to your bookmarks bar and start using immediately. And since they are just pieces of javascript code, you can customize them as much as you want and have your own version of the bookmarklet tailored to your needs.&lt;/p&gt;

&lt;p&gt;So there you go, the power of bookmarklets. I hope this will help you become more productive in your everyday browsing or at least alleviate some of the pains. If you have any questions, send me an &lt;a href="//mailto:shuvophoenix@gmail.com"&gt;email&lt;/a&gt; or drop me a &lt;a href="https://twitter.com/scionofbytes"&gt;tweet&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Generating the Mandelbrot Set</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Tue, 30 Mar 2021 15:15:25 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/generating-the-mandelbrot-set-jk4</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/generating-the-mandelbrot-set-jk4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://badgerbadgerbadgerbadger.dev/generating-the-mandelbrot-set/"&gt;https://badgerbadgerbadgerbadger.dev/generating-the-mandelbrot-set/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Update
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The shared github repo for the end product of this post is in version 2 state. &lt;a href="https://github.com/lovasoa"&gt;@lovasoa&lt;/a&gt; kindly contributed a pull request which massivley improved performance and allowed the rendering to happen at full screen with much better speed and taught me many things in the process. I've added these improvements as an updated section at the end of the post. Many thanks &lt;a href="https://github.com/lovasoa"&gt;@lovasoa&lt;/a&gt;. The first version, on which most of this post is based is still available for perusal at &lt;a href="https://github.com/ScionOfBytes/smooth-mandelbrot/tree/v1.0"&gt;https://github.com/ScionOfBytes/smooth-mandelbrot/tree/v1.0&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, somewhere in late April, I became fascinated with the Mandelbrot Set. I came across an interesting &lt;a href="https://www.youtube.com/watch?v=FFftmWSzgmk"&gt;video&lt;/a&gt; by &lt;a href="https://www.bensparks.co.uk/"&gt;Ben Sparks&lt;/a&gt; on &lt;a href="https://www.youtube.com/channel/UCoxcjq-8xIDTYp3uz647V5A"&gt;Numberphile&lt;/a&gt; where he speaks of them. He starts off with the idea of stable iterations first, on a one-dimensional number line and then, on a two dimensional complex plane, demonstrating how-- on iterating on the square of a given number-- the result can either blow up and tend towards infinity or remain stable and tend towards 0. Or at least that's the idea I got. Someone more math-y correct me if I'm wrong.&lt;/p&gt;

&lt;p&gt;The visualisations in the video were quite nicely done and I got an itch to try them myself. My best friend has been studying Daniel Shiffman's &lt;a href="https://www.youtube.com/watch?v=HerCR8bw_GE&amp;amp;list=PLRqwX-V7Uu6Zy51Q-x9tMWIv9cueOFTFA&amp;amp;index=1"&gt;Code! Programming with p5.js for Beginners&lt;/a&gt; and that reminded me of my own early years in programming when I kept myself motivated by building trippy animations and other cool stuff you can do with a library like p5.js (though back then only &lt;a href="https://processing.org"&gt;Processing&lt;/a&gt; existed).&lt;/p&gt;

&lt;p&gt;So I thought, why not try my hand at generating the Mandelbrot Set?&lt;/p&gt;

&lt;h2&gt;
  
  
  Complex Numbers
&lt;/h2&gt;

&lt;p&gt;First, we need to talk about Complex Numbers. There are &lt;a href="https://www.youtube.com/watch?v=gHUHZXjpwOE"&gt;a ton&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=sZrOxm5Gszk"&gt;of videos&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=SP-YJe7Vldo"&gt;on YouTube&lt;/a&gt; about Complex Numbers. I won't go into the details of what they are &lt;em&gt;exactly&lt;/em&gt; or why they exist. Let's just say that for our purposes we can think of complex numbers existing on a line perpindicular to our regular number line. If you take a piece of graph paper and mark a line from left to right, and then another line that crosses that line at 90 degrees, from top to bottom, you can imagine that the horizontal line represents the real number line, and the vertical line represents the complex number line. This gives us a Complex &lt;em&gt;Plane&lt;/em&gt; to work with.&lt;/p&gt;

&lt;p&gt;I highly suggest watching the videos I've linked to really grasp the ideas behind Complex Numbers and how they represent a plane, but for the purpose of this post, let's just say we have a grid and two numbers to work with, one number representing a horizontal position on the grid, and one representing a vertical. And these give us a point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mandelbrot Set
&lt;/h2&gt;

&lt;p&gt;The Mandelbrot set starts with the idea of iteration. Say you have a number &lt;strong&gt;z&lt;/strong&gt;. This is a complex number of two parts, but for now let's imagine it's a simple number.&lt;/p&gt;

&lt;p&gt;Let's square &lt;strong&gt;z&lt;/strong&gt; giving us &lt;strong&gt;z&lt;sup&gt;2&lt;/sup&gt;&lt;/strong&gt;. Let's take another number &lt;strong&gt;c&lt;/strong&gt; and add it to our result to get &lt;strong&gt;z&lt;sup&gt;2&lt;/sup&gt; + c&lt;/strong&gt;. Let this be our new &lt;strong&gt;z&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We have the operation &lt;strong&gt;z' = z&lt;sup&gt;2&lt;/sup&gt; + c&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can do this again but this time, instead of our original &lt;strong&gt;z&lt;/strong&gt; we are using our new value &lt;strong&gt;z'&lt;/strong&gt;. This way, we keep feeding the result of the previous operation into the next one. We keep iterating like this until we have a reason to stop. We can start with a value &lt;strong&gt;z = 0&lt;/strong&gt;, but it's the &lt;strong&gt;c&lt;/strong&gt; that's noteworthy here.&lt;/p&gt;

&lt;p&gt;The Mandelbrot Set, as I understand it, is the set of all numbers in the Complex Plane that, when put under this iteration, does not blow up. The &lt;strong&gt;c&lt;/strong&gt; in our equation is the number we're testing.&lt;/p&gt;

&lt;p&gt;In this context, if, upon iteration the value of &lt;strong&gt;z&lt;/strong&gt; becomes larger than 2 at any time, we consider it to have &lt;em&gt;blown up&lt;/em&gt;. At this point we know that it will tend towards infinity.&lt;/p&gt;

&lt;p&gt;So, when speaking of the Mandelbrot set what we do is, for each point in our grid, we test to see if, when put under iteration via the &lt;strong&gt;z' = z&lt;sup&gt;2&lt;/sup&gt; + c&lt;/strong&gt; operation, where &lt;strong&gt;c = the point&lt;/strong&gt;, if the value blows up, then the point is not part of the Mandelbrot Set. And if it does not, then it is.&lt;/p&gt;

&lt;p&gt;Honestly, this is all very math-y and I am going to stop with the Math now. I highly recommend you watch the above videos for a solid understanding of Complex Numbers and the Mandelbrot Set. From here on out, I will be talking mostly code. &lt;/p&gt;

&lt;p&gt;Let's say we have a grid of pixels, and each pixel can be represented by &lt;code&gt;x / y&lt;/code&gt; coordinates. Let's also say that we have a function (the programming kind, not the math kind) &lt;code&gt;testMandelbrot&lt;/code&gt; which takes a coordinate &lt;code&gt;c&lt;/code&gt; and tells us if &lt;code&gt;c&lt;/code&gt; is in the Mandelbrot Set or not. That function and most of the math we need to perform is in &lt;a href="https://github.com/ScionOfBytes/smooth-mandelbrot/blob/master/js/compy-stuff.js#L20"&gt;this file&lt;/a&gt;. As you can see, it requires very little code.&lt;/p&gt;

&lt;p&gt;For a proper understanding of why all this math is happening (and especially if my attempt to explain wasn't sufficient), have a look at these videos.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=NGMRB4O922I&amp;amp;t=16s"&gt;The Mandelbrot Set - Numberphile&lt;/a&gt; - More about the Mandelbrot Set.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=MwjsO6aniig"&gt;Mandelbrot Set on Vsauce&lt;/a&gt; - A very entertaining and educational video by the beloved Michael Stevens.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then read this excellent little page by Karl Sims-- &lt;a href="https://www.karlsims.com/julia.html"&gt;Understanding Julia and Mandelbrot Sets by Karl Sims&lt;/a&gt;-- this one helped me a &lt;em&gt;lot&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Attempts
&lt;/h2&gt;

&lt;p&gt;My first attempt was semi-successful. I made many mistakes, wrote a lot of messy code, and managed to get something going that rendered very slowly even for a 450 x 200 resolution canvas. You can find it &lt;a href="https://editor.p5js.org/scionofbytes/full/UfCfqKVrY"&gt;here&lt;/a&gt;. If you play around with the resolution, you will notice that upping it slightly to 600 x 350 will make it render significantly slower than the initial 400 x 200 setting. This is no surprise. It might only be an increase of 200 pixels horizontally and 150 pixels vertically, but overall, there are now 30K more pixels to scan than before.&lt;/p&gt;

&lt;p&gt;I also have a version of this with cleaner code on &lt;a href="https://openprocessing.org"&gt;https://openprocessing.org&lt;/a&gt; (&lt;a href="https://www.openprocessing.org/sketch/707203"&gt;this one&lt;/a&gt;), but I stressed it out too much in terms of resolution and openprocessing is taking too long to render it.&lt;/p&gt;

&lt;p&gt;This is how it looks, by the way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jy8I6dhv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/rKzuPXj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jy8I6dhv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/rKzuPXj.png" alt="p5 editor result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided it was running slow because it was in Javascript. I am ashamed to say that even after five years of working in the industry I still have such naive thoughts. The truth is my code was merely bad and lacking any sort of polish or optimisation. &lt;/p&gt;

&lt;p&gt;So the next step of my plan was to try to do the same thing in &lt;a href="https://processing.org"&gt;Processing 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I started small:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ugJX0-dj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/QyoLM9U.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ugJX0-dj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/QyoLM9U.png" alt="kotlin version low res"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then went a tiny bit bigger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NRIBpBOw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/OxFtnsN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NRIBpBOw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/OxFtnsN.png" alt="kotlin version higher res"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These took way too long to generate, though, with the first one clocking in at 2251 ms and the second at 7869 ms.&lt;/p&gt;

&lt;p&gt;I was still making mistakes here. I was using a full package to do the complex math and several other overly complicated things. There wasn't a noticeable performance gain over the JS code.&lt;/p&gt;

&lt;p&gt;And then I decided to do something &lt;em&gt;really&lt;/em&gt; stupid. I wanted to see a highly zoomed-in version of the Mandelbrot Set and admire the beautiful fractal patterns. So I thought, why not make a &lt;em&gt;really&lt;/em&gt; large image and then zoom into that using a regular image viewer and admire the fractals that way?&lt;/p&gt;

&lt;p&gt;So I tried to generate a 12000x10000 image. That's 120 million pixels. A 120 mega-pixel image. I tried to generate that on my laptop. And I made a royal mess of it, too. Not only did it take over an hour to do, I forgot to save it, so just to show you guys that image I'll have to let my laptop run hot for an hour, again, and it won't even be that great an image. You'll get to see it anyway.&lt;/p&gt;

&lt;p&gt;But let's take a step back.&lt;/p&gt;

&lt;p&gt;I didn't want to just make a 120 megapixel image. I wanted to make a 12 gigapixel one. But the moment I tried to allocate an array that large, JVM told me to stick my head where the sun don't shine. Okay, 12K x 10K it would be. I wanted this image to look extra special so I decided to make some smaller ones look really good as practice.&lt;/p&gt;

&lt;p&gt;First a 1200 x 1000 that took 38,785ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2HOtO6YP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/nI51GLq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2HOtO6YP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/nI51GLq.png" alt="kotlin version 1200x1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is pretty nice looking, actually, but let's see if we can do something prettier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wVLJFfMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/fHBJM8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wVLJFfMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/fHBJM8q.png" alt="kotlin version 1200x1000 with extra greys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each of the points &lt;em&gt;not&lt;/em&gt; inside the Mandelbrot set, I'm taking the number of iterations it took before it blew up and mapping that from 0 - 200 (since 200 is the maximum number of iterations I'm checking) to 0 - 255 (8 bit greyscale color range).&lt;/p&gt;

&lt;p&gt;Now we can not only see the scattered Mandelbrot islands, we can also see the connections between them. These connections are too thin to show up on such a low resolution image, but the surrounding shading gives them away.&lt;/p&gt;

&lt;p&gt;Let's try something in another color.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zmbhQeS---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/d0Mt31G.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zmbhQeS---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/d0Mt31G.png" alt="kotlin version 1200x1000 with red borders"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this I decided to color the inside of the Set fully black, and let the outside be mapped from 0 (full black) to #FF0000(full red), giving us a vivid red border.&lt;/p&gt;

&lt;p&gt;Let's do something even more colorful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MowwcUyz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/xvEKaDS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MowwcUyz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/xvEKaDS.png" alt="kotlin version 1200x1000 with multi-color borders"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This looks closer to what I see in most online visualisations. Here I'm using the same iterations-till-blowup value and then mapping that to a hue (think &lt;a href="https://en.wikipedia.org/wiki/HSL_and_HSV"&gt;HSL&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Now I'll leave you with the 12K x 10K resolution version of this image. I won't render it on this page directly because that would tank its load time. You can click on &lt;a href="https://i.imgur.com/HWGjDy4.jpg"&gt;this link&lt;/a&gt; to view it. If you compare it to the low res ones you should see a significantly greater amount of detail visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Sensible Approach
&lt;/h2&gt;

&lt;p&gt;I wanted to see the tiniest details in the fractal pattern and I thought the way to go about doing that would be to create a very high resolution image. But there's only so much a tiny computer can do. And once you cross the screen resolutions currently available (4K is only 3840 x 2160, compared to the 12K x 10K image I generated which can never be fully represented on a 4K screen), you'll have to zoom in a lot to see details. There is a never a time when you can see the full fractal in absolute detail.&lt;/p&gt;

&lt;p&gt;Then there's the matter of how long it takes to generate one of these. If you want to play around with iteration values or colors or any other paramter, you would have to generate the static image all over again. Not fun.&lt;/p&gt;

&lt;p&gt;I decided to watch some more YouTube videos and figure out how to do this better. I turned to Daniel Shiffman, one of the best YouTube educators I know of in the programming space.&lt;/p&gt;

&lt;p&gt;A new plan formed in my mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zooming and Panning
&lt;/h2&gt;

&lt;p&gt;I fired up the p5 editor again. Since I was planning to keep the resolution fairly small, I figured I could keep things in the browser. Or rather, &lt;em&gt;because&lt;/em&gt; I wanted to keep it in the browser, I decided to go with the low, low resolution of 400 x 400.&lt;/p&gt;

&lt;p&gt;I also made it so the image can be zoomed using &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; and panned using the arrow keys. It still happens really slowly, but at least it works.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In case the embed is not working, go &lt;a href="https://editor.p5js.org/scionofbytes/full/kdAFrs4jI"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I decided to get a little bit fancy and let the image appear iteration by iteration all the way up to 200. The iterations here are the maximum number of iterations at which we stop testing if the &lt;strong&gt;z&lt;/strong&gt; value will grow beyond 2 (blow up). As you can see, the more iterations performed, the more detailed the picture. This effect is clearer when you are zoomed into one of the edges. You can see the details becoming finer.&lt;/p&gt;

&lt;p&gt;Zooming and panning is the way to go forward. If you are to look at only a part of the image at a time, why bother rendering the rest? Graphics programmers have known this since ages beyond reckoning. It took me a failed attempt at generating a high res image at speed to think of doing the same thing (and ideas from Shiffman's video).&lt;/p&gt;

&lt;h2&gt;
  
  
  Making It Smoother
&lt;/h2&gt;

&lt;p&gt;This rendering is still very clunky, and quite slow. Even at smaller iterations. I want at least 200 iterations to get good fractal detail which will make this run really slowly. But Javascript is single-threaded and there isn't any more I can squeeze out of it. Or can I?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"&gt;Web Workers&lt;/a&gt; have been a thing for a while. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas"&gt;OffScreen Canvases&lt;/a&gt; are somewhat newer (and not very well supported on non-Chrome browsers). The demo I present here might not work if you're not on the latest Chrome and I apologise for that. Making it work in other ways is possible but too tedious and not something I want to do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I've since learned that a Uint8ClampedArray works much better than an OffScreenCanvas both in terms of performance and browser compatibility. It needs more code for computing array indices and colors but overall much better outcome. See the end of the post for details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Play around with it. ~Click on any part of the image to center it~ Click and drag to pan and use &lt;code&gt;alt/option + up arrow/down arrow&lt;/code&gt; or the scroll wheel/pad to zoom in and out. You can zoom in pretty deep before the fractal finally gives up; numbers on a computer only have so much precision.&lt;/p&gt;

&lt;p&gt;The entire code for it (which is small), can be found here: &lt;a href="https://github.com/BadgerBadgerBadgerBadger/smooth-mandelbrot"&gt;https://github.com/BadgerBadgerBadgerBadger/smooth-mandelbrot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's talk about how it works and why it is so much smoother than the previous version (though not as smooth as I would ultimately like it to be).&lt;/p&gt;

&lt;h2&gt;
  
  
  Divide and Conquer
&lt;/h2&gt;

&lt;p&gt;Web Workers allow us to run honest-to-goodness OS threads and offload tasks to them. My main goals for my worker threads were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Free up the main thread for processing user input.&lt;/li&gt;
&lt;li&gt;Break up the rendering task between multiple workers to speed it up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We start with the &lt;code&gt;sketch.js&lt;/code&gt; as our entry point.&lt;/p&gt;

&lt;p&gt;We set up a canvas, attach a click and keyboard listener, and add it to our DOM. We also start the &lt;code&gt;painter.js&lt;/code&gt; worker which, as the name suggests, will be doing all the work of painting our canvas. And then we send a &lt;code&gt;setup&lt;/code&gt; command to the painter asking it to set everything up and do the first paint.&lt;/p&gt;

&lt;p&gt;The interesting bits here are&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offScreen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transferControlToOffscreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offScreen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transfers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;offScreen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen"&gt;transfer control of our canvas to an offscreen version&lt;/a&gt; which we then send to our painter. We are detaching the painting part and the displaying part. Though the canvas is still part of our DOM and displayed as such, the painting is done by the painter in a different thread and the results show up automatically. Also note the function call we make to send the offscreen canvas to our painter worker. That second parameter is an array of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Transferable"&gt;Transferrable&lt;/a&gt; objects to send to the worker.&lt;/p&gt;

&lt;p&gt;Transferrable objects, in short, are those that you can send to a Web Worker and it will be a true memory transfer. The sending thread loses reference to the transferrable (if I try to do anything with &lt;code&gt;offScreen&lt;/code&gt; after this bit of code, I will get an error), and the receiving thread assumes full control. &lt;/p&gt;

&lt;p&gt;Which is exactly what we want-- we let the painter do the job of painting so our main thread is free to interact with the user. It does so in two ways. A mouse click event and a key type event. I won't go into the code of those. They aren't very interesting or pertinent to this article. Let's just say, they work and they send a &lt;code&gt;draw&lt;/code&gt; command to the painter with some parameters.&lt;/p&gt;

&lt;p&gt;There is a lot going on here but we need not focus on all of it. Let's look at the interesting bits.&lt;/p&gt;

&lt;p&gt;First, the &lt;code&gt;setup&lt;/code&gt;. I'm assigning the canvas and its context, retrieved from the setup event sent by the main thread, to the global variables for the painter. Then I start divvying up the tasks. I decided, somewhat arbitrarily, that I want the task of painting to be broken up into 8 parts. I'm sure there is a way to figure out what the optimal number should be, but I just decided to go for 8. So I create 8 &lt;code&gt;acolytes&lt;/code&gt;. These acolytes get only a portion of the canvas to work on. I'm slicing up the canvas into 8 vertical slices. We need to know the width of each segment: &lt;code&gt;segmentLength = canvas.width / numOfAcolytes&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Note that I'm not actually slicing up the original canvas. Instead, I'm creating baby canvases, each given to one of the acolytes. Later, once the acolytes have drawn on one of these canvases, we will take the resulting image and paint it onto our main canvas. I hope this analogy of a master painter and their acolytes provides a good analogy for how this process works.&lt;/p&gt;

&lt;p&gt;Something I should have talked about much earlier is that the Mandelbrot Set lives somewhere between -3 and 1 on the Complex Line and -2 and 2 on the Real Line. Since the canvas is hundreds of pixels wide and similarly high, we need to map these values from a large range into a smaller one. Which is easy enough to do with a simple &lt;code&gt;map&lt;/code&gt; function. &lt;/p&gt;

&lt;p&gt;But the acolytes, unless they know exactly by how much each of their canvases are offset from the main canvas, won’t do this computation correctly. Hence, we need to provide them with this info. They also need to know the dimensions of the original canvas so we send that in, too. Furthermore, they need to know what range they need to map their canvases to. So we also pass in the ranges for the &lt;strong&gt;x&lt;/strong&gt; direction and for the &lt;strong&gt;y&lt;/strong&gt; direction.&lt;/p&gt;

&lt;p&gt;Most of this info is only sent during setup. We don't change the size of the original canvas or the number of acolytes (and thus the width of each acolyte's segment) during the lifetime of the app. Only the boundaries change when we pan or zoom and we send this information when we issue &lt;code&gt;draw&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;So let's talk about the &lt;code&gt;draw&lt;/code&gt; command, our second case in the switch-case. When receiving a &lt;code&gt;draw&lt;/code&gt; command, the painter could receive mouse or keyboard events from the main thread. We allow users to pan the canvas by clicking on a point in the image and we pan so that that point becomes the new center of the image. The &lt;code&gt;if (mousePos.x !== undefined) {&lt;/code&gt; block of the &lt;code&gt;if&lt;/code&gt; statement takes care of this. I won't explain the math here. Suffice it to say, that based on the position of the mouse click, we adjust the boundaries. Similarly, in the &lt;code&gt;} else if (zoomBy) {&lt;/code&gt; block, we adjust the boundaries so we are zooming in/out of the image.&lt;/p&gt;

&lt;p&gt;Either way, once the new boundaries are computed, the global &lt;code&gt;xBounds&lt;/code&gt; and &lt;code&gt;yBounds&lt;/code&gt; variables are updated and the &lt;code&gt;draw&lt;/code&gt; function of the painter is called. This is a really simple function that sends the boundaries and number of iterations to the acolytes and asks them to paint their pieces of the canvas. The acolytes do that (we'll talk about that next), and send these images back which we capture in the &lt;code&gt;onAcolyteMessage&lt;/code&gt; function. We know which acolyte sent the image, hence we know which piece of the canvas they were given and we paint the image onto the canvas at that place.&lt;/p&gt;

&lt;p&gt;Note, I am not waiting to collect all the responses from each acolyte for each draw command before updating the canvas. That would probably lead to smoother looking updates. Right now, if you pan or zoom quickly you can see the update happening in bands as each acolyte draws their portion of the image.&lt;/p&gt;

&lt;p&gt;The code for the acolytes is quite simple as well. In the &lt;code&gt;setup&lt;/code&gt; command we capture the canvas and context for drawing and we capture the boundaries. And then it draws once. Further, when it receives more draw commands, it updates its knowledge of the boundaries and draws its part of the image again.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; function itself isn't very different from what I've done in previous versions of the generator. Iterate over the pixels, map it into the correct range, figure out if it is part of the set or not and color accordingly. It then gets a copy of the image from its canvas and sends that to the painter (again via Transferrable). It seems while a normal canvas can have its drawing part detached and in a different thread with updates to the drawing appearing on the canvas, the same is not true if you use an OffscreenCanvas instance. This is still an experimental API so it will be really interesting to see how it develops. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: You might have noticed that the acolytes are receiving &lt;code&gt;maxIters&lt;/code&gt; as well. Currently they aren't doing anything interesting with this variable. I've set it to a fixed value and it stays there. I could potentially play with it but I don't want to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's mostly it. I have a main thread which kicks everything off and then handles user input and sends to the painter. The painter decides the image boundaries based on user input (panning/zooming), and then offloads the drawing of sections of the canvas to the acolytes.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the post. Feedback is appreciated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updates
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/lovasoa"&gt;@lovasoa&lt;/a&gt; was kind enough to contribute several optimisation updates that make smooth mandelbrot worth its name. These include but are not limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Uint8ClampedArray in the Acolytes for setting color values and passing that back instead of the heavier Canvas objects. Additionally with the non-standard OffScreenCanvas not in use the demo actually works on more browsers.&lt;/li&gt;
&lt;li&gt;Inlining a lot of the Complex math.&lt;/li&gt;
&lt;li&gt;Introducing a mouse-based scroll.&lt;/li&gt;
&lt;li&gt;Flooring numbers using a bitwise OR operator that looks like black magic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd recommend taking a look at his &lt;a href="https://github.com/ScionOfBytes/smooth-mandelbrot/pull/1"&gt;PR&lt;/a&gt; to get a feel for the changes.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Yet Another mTLS Tutorial</title>
      <dc:creator>Shaun</dc:creator>
      <pubDate>Fri, 12 Mar 2021 14:59:26 +0000</pubDate>
      <link>https://forem.com/badgerbadgerbadgerbadger/yet-another-mtls-tutorial-10pp</link>
      <guid>https://forem.com/badgerbadgerbadgerbadger/yet-another-mtls-tutorial-10pp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://badgerbadgerbadgerbadger.dev/yet-another-m-tls-tutorial/" rel="noopener noreferrer"&gt;https://badgerbadgerbadgerbadger.dev/yet-another-m-tls-tutorial/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've come across many mTLS tutorials on the internet but none of them took me through the process, end-to-end, in a satisfying fashion. I hope to do in this tutorial is to start with the ideas of TLS and mTLS and conclude with an implementation you could reasonably productionize. Although we'll be using a Golang server as our backend and Traefik as our reverse proxy, these ideas translate to other technologies as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's Start with the Barebones
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Serve Me!
&lt;/h2&gt;

&lt;p&gt;In a traditional TLS setup, the kind you encounter every time you see that green padlock icon in your browser's address bar, it is the browser verifying the server's identity. When you go to a website you access it via an address such as &lt;a href="https://badgerbadgerbadgerbadger.dev" rel="noopener noreferrer"&gt;badgerbadgerbadgerbadger.dev&lt;/a&gt;. If the website has TLS support (and if you encounter one in 2021 that &lt;em&gt;does not&lt;/em&gt; have TLS support, seriously, don't use it), the browser will ask it to send over its server-side certificate in order to verify that the website is actually who it says it is. The website sends back such a certificate which has a &lt;a href="https://www.godaddy.com/garage/whats-a-fully-qualified-domain-name-fqdn-and-whats-it-good-for/" rel="noopener noreferrer"&gt;&lt;em&gt;CommonName&lt;/em&gt; (also known as a &lt;em&gt;Fully Qualified Domain Name&lt;/em&gt;, or &lt;em&gt;FQDN&lt;/em&gt;)&lt;/a&gt; field which &lt;em&gt;should&lt;/em&gt; match the address you entered in the address bar. For &lt;a href="https://badgerbadgerbadgerbadger.dev" rel="noopener noreferrer"&gt;https://badgerbadgerbadgerbadger.dev&lt;/a&gt; the returned certificate &lt;em&gt;should&lt;/em&gt; have a &lt;em&gt;CommonName&lt;/em&gt; of &lt;strong&gt;badgerbadgerbadgerbadger.dev&lt;/strong&gt;. Your browser will see that the &lt;em&gt;CommonName&lt;/em&gt; and the name in the address match and it will be satisfied that you are indeed visiting a website who is what it claims to be.&lt;/p&gt;

&lt;p&gt;"But wait!" I hear you say, "Couldn't anyone send back a certificate with a &lt;em&gt;CommonName&lt;/em&gt; field set to whatever?" Astute observation. Indeed, anyone can create a certificate, attach whatever &lt;em&gt;CommonName&lt;/em&gt; they choose and send it back to your browser...&lt;/p&gt;

&lt;p&gt;...except, they can't.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://support.dnsimple.com/articles/what-is-certificate-authority/" rel="noopener noreferrer"&gt;&lt;em&gt;Certificate Authorities (CAs)&lt;/em&gt;&lt;/a&gt; come in. These are organizations that have (allegedly) gone through rigorous audits and whose trustworthiness has been verified to an extent that if they say that a website is who it says it is, that's good enough for browsers to trust that website. And how does this process work, you say? We'll get to that when we get to the more hands-on section of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ti(m)e to Reciprocate
&lt;/h2&gt;

&lt;p&gt;Alright, so web browsers know who the server is. The server can be trusted. But can the browser be trusted? Maybe, maybe not. But to make our use case more practical let's move away from the example of an end-user using a browser and move to two backend machines talking to each other. Let's imagine we have a traditional client-server architecture where &lt;em&gt;Machine A&lt;/em&gt; needs to get data from &lt;em&gt;Machine B&lt;/em&gt; at random intervals, and the way that &lt;em&gt;Machine B&lt;/em&gt; has decided it wants &lt;em&gt;Machine A&lt;/em&gt; to authenticate is Mutual TLS.&lt;/p&gt;

&lt;p&gt;If you understood how a server identified itself to the client by using a certificate trusted by a third party, you're already most of the way to understanding mTLS: it's kind of in the name.&lt;/p&gt;

&lt;p&gt;In mTLS (&lt;em&gt;Mutual&lt;/em&gt; TLS) the server sends its certificate as usual, but the client has to send one too. Let's say &lt;em&gt;Machine B&lt;/em&gt; (the server) identifies itself as &lt;code&gt;machineb.com&lt;/code&gt; and has a certificate with a matching &lt;em&gt;CommonName&lt;/em&gt;, that's one side of the equation. In order for the other side (&lt;em&gt;Machine A&lt;/em&gt;, the client) to do its part, it also needs to obtain a certificate. This certificate doesn't have to have a specific &lt;em&gt;CommonName&lt;/em&gt;. It doesn't &lt;em&gt;have&lt;/em&gt; to be &lt;code&gt;machinea.com&lt;/code&gt;. But if we're only using public CAs, the easiest thing for &lt;em&gt;Machine A&lt;/em&gt; to do is to prove to a CA that they own &lt;code&gt;machinea.com&lt;/code&gt; and then be issued a certificate for that &lt;em&gt;CommonName&lt;/em&gt;. &lt;em&gt;Machine B&lt;/em&gt;, on its end will know to expect requests from &lt;em&gt;Machine A&lt;/em&gt; authenticated by a certificate that has the &lt;em&gt;CommonName&lt;/em&gt; of &lt;code&gt;machinea.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Are there other ways to do this? Sure. &lt;em&gt;Machine B&lt;/em&gt; could host its own CA and issue a certificate to &lt;em&gt;Machine A&lt;/em&gt;. That way &lt;em&gt;Machine B&lt;/em&gt; knows exactly what to expect since it's the one that issued the certificate.&lt;/p&gt;

&lt;p&gt;I'll end this section with a link to a &lt;a href="https://medium.com/sitewards/the-magic-of-tls-x509-and-mutual-authentication-explained-b2162dec4401" rel="noopener noreferrer"&gt;really nice article that has some informative diagrams&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's Get Our Hands Dirty
&lt;/h1&gt;

&lt;p&gt;It's time to get into some hands-on learning. Let's start with our backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflect Thine Headers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":6969"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%v: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're unfamiliar with &lt;a href="https://golang.org/" rel="noopener noreferrer"&gt;Golang&lt;/a&gt; here's what the above code does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starts a http server on port &lt;a href="https://i.pinimg.com/474x/32/c6/db/32c6db68576a9535537cdc0ce82a3a1f.jpg" rel="noopener noreferrer"&gt;6969&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;responds with any headers sent to it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to implement this in your language of choice if you don't have the Golang toolchain set up. Once you have your backend running, let's move on to the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Curl It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; http://localhost:6969/headers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What, did you think we were going to write some HTML and JS code? Do I look like I'm in a masochistic mood? &lt;a href="https://curl.se/" rel="noopener noreferrer"&gt;Curl&lt;/a&gt; is good enough for our needs. If you don't have it installed, google (or your choice of search engine) is your friend.&lt;/p&gt;

&lt;p&gt;If you managed to run that curl command and your backend has been running, you should get a response like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-Agent: curl/7.64.1
Accept: */*
Heyo: mayo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, we've had our headers reflected back to us. So far so good.&lt;/p&gt;

&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; go ahead and implement mTLS right there in our backend Golang code but in a robust production system you want your concerns to be separated. Our backend shouldn't have to worry about TLS protocol stuff, so let's move all of that to our &lt;a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/" rel="noopener noreferrer"&gt;reverse proxy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For our choice of reverse proxy there's always good old &lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt; which has a straightforward way to &lt;a href="https://smallstep.com/hello-mtls/doc/server/nginx" rel="noopener noreferrer"&gt;configure mTLS&lt;/a&gt;. If you're feeling especially nostalgic you might even find yourself reaching for &lt;a href="https://httpd.apache.org/" rel="noopener noreferrer"&gt;Apache&lt;/a&gt; where you can also &lt;a href="https://blog.behrang.org/2019/08/11/ssl-mututal-authentication-apache-http-client.html" rel="noopener noreferrer"&gt;configure mTLS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But we're hip and cool (or at least we like to think so) so we're going to be using the (relatively) new kid on the block &lt;a href="https://traefik.io/traefik/" rel="noopener noreferrer"&gt;Traefik Proxy&lt;/a&gt;. I'm sure you can figure out how to &lt;a href="https://doc.traefik.io/traefik/getting-started/install-traefik/" rel="noopener noreferrer"&gt;install it&lt;/a&gt;, by yourself. Do that and come back once you've read up on &lt;a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/" rel="noopener noreferrer"&gt;Traefik's configuration&lt;/a&gt; management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traefiking
&lt;/h2&gt;

&lt;p&gt;Back? Figured out how to load &lt;a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-static-configuration" rel="noopener noreferrer"&gt;static&lt;/a&gt; and &lt;a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-dynamic-configuration" rel="noopener noreferrer"&gt;dynamic&lt;/a&gt; configuration in Traefik? Good, so here's what we'll be using.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I'm using version 2.2.1, the latest (at the time of this writing) is 2.4.x, but I don't think there should be a big difference in our experiences using either one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Static Config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;## Static configuration&lt;/span&gt;
&lt;span class="nn"&gt;[log]&lt;/span&gt;
&lt;span class="py"&gt;level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DEBUG"&lt;/span&gt;

&lt;span class="nn"&gt;[api]&lt;/span&gt;
&lt;span class="py"&gt;dashboard&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;debug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;insecure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[entryPoints]&lt;/span&gt;
&lt;span class="nn"&gt;[entryPoints.web]&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;":8089"&lt;/span&gt;

&lt;span class="nn"&gt;[entryPoints.websecure]&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;":443"&lt;/span&gt;

&lt;span class="nn"&gt;[providers]&lt;/span&gt;
&lt;span class="nn"&gt;[providers.file]&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"path/to/dynamic-config/directory"&lt;/span&gt;

&lt;span class="nn"&gt;[accessLog]&lt;/span&gt;
&lt;span class="nn"&gt;[accessLog.fields]&lt;/span&gt;
&lt;span class="py"&gt;defaultMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"keep"&lt;/span&gt;

&lt;span class="nn"&gt;[accessLog.fields.names]&lt;/span&gt;
&lt;span class="py"&gt;defaultMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"keep"&lt;/span&gt;

&lt;span class="nn"&gt;[accessLog.fields.headers]&lt;/span&gt;
&lt;span class="py"&gt;defaultMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"keep"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're starting a dashboard where we can view our configurations&lt;/li&gt;
&lt;li&gt;We're starting a web listener (regular http) at port 8089&lt;/li&gt;
&lt;li&gt;We're starting a secure listener (https) at port 443&lt;/li&gt;
&lt;li&gt;We're providing the &lt;em&gt;directory&lt;/em&gt; where our dynamic config is stored (you can have a ton of dynamic config files and they all get merged into one big config)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at the dynamic config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[http]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.traeifk-api]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`traefik.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api@internal"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`server.mtls-tut.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mtls-tut"&lt;/span&gt;

&lt;span class="nn"&gt;[http.services]&lt;/span&gt;
&lt;span class="nn"&gt;[http.services.mtls-tut.loadBalancer]&lt;/span&gt;
&lt;span class="nn"&gt;[[http.services.mtls-tut.loadBalancer.servers]]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:6969/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're defining a &lt;a href="https://doc.traefik.io/traefik/routing/routers/" rel="noopener noreferrer"&gt;&lt;em&gt;router&lt;/em&gt;&lt;/a&gt; to route any requests that match the &lt;a href="https://doc.traefik.io/traefik/routing/routers/#rule" rel="noopener noreferrer"&gt;&lt;em&gt;rule&lt;/em&gt;&lt;/a&gt; &lt;em&gt;Host(&lt;code&gt;server.mtls-tut.local&lt;/code&gt;)&lt;/em&gt;. Any requests that arrive with the host value set to &lt;code&gt;server.mtls-tut.local&lt;/code&gt; will get picked up by the router &lt;code&gt;mtls-tut&lt;/code&gt;. The same is done for the traefik api (which is used by its dashboard).&lt;/li&gt;
&lt;li&gt;We're defining a &lt;a href="https://doc.traefik.io/traefik/routing/services/" rel="noopener noreferrer"&gt;&lt;em&gt;service&lt;/em&gt;&lt;/a&gt; called &lt;code&gt;mtls-tut&lt;/code&gt; which load-balances to our backend server running at port &lt;a href="https://i.imgflip.com/4k39g9.jpg" rel="noopener noreferrer"&gt;6969&lt;/a&gt;. In our router configuration we've defined this service as the one that should be serving all requests for &lt;code&gt;server.mtls-tut.local&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now start Traefik however you prefer (docker, binary, whatever), make sure it can access both the static and dynamic files and you've pointed it to pick up the static config. If you did everything right Traefik will start up and print logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO[0000] Configuration loaded from file: /Users/shak/installs/traefik/traefik.toml
INFO[2023-02-04T10:01:47+01:00] Traefik version 2.2.1 built on 2020-04-29T18:09:41Z
DEBU[2023-02-04T10:01:47+01:00] Static configuration loaded {"global":{"checkNewVersion":true},"serversTransport":{"maxIdleConnsPerHost":200},"entryPoints":{"traefik":{"address":":8080","transport":{"lifeCycle":{"graceTimeOut":10000000000},"respondingTimeouts":{"idleTimeout":180000000000}},"forwardedHeaders":{},"http":{}},"web":{"address":":8089","transport":{"lifeCycle":{"graceTimeOut":10000000000},"respondingTimeouts":{"idleTimeout":180000000000}},"forwardedHeaders":{},"http":{}},"websecure":{"address":":443","transport":{"lifeCycle":{"graceTimeOut":10000000000},"respondingTimeouts":{"idleTimeout":180000000000}},"forwardedHeaders":{},"http":{}}},"providers":{"providersThrottleDuration":2000000000,"file":{"directory":"/Users/shak/projects/mtls-example/traeifk/dynamic","watch":true}},"api":{"insecure":true,"dashboard":true,"debug":true},"log":{"level":"DEBUG","format":"common"},"accessLog":{"format":"common","filters":{},"fields":{"defaultMode":"keep","names":{"defaultMode":"keep"},"headers":{"defaultMode":"keep"}}}}
...
...
INFO[2023-02-04T10:01:47+01:00] Starting provider *file.Provider {"directory":"/Users/shak/projects/mtls-example/traeifk/dynamic","watch":true}
INFO[2023-03-04T10:01:47+01:00] Starting provider *traefik.Provider {}
DEBU[2023-03-04T10:01:47+01:00] Configuration received from provider file: {"http":{"routers":{"mtls-tut":{"service":"mtls-tut","rule":"Host(`server.mtls-tut.local`)"},"traeifk-api":{"service":"api@internal","rule":"Host(`traefik.local`)"}},"services":{"mtls-tut":{"loadBalancer":{"servers":[{"url":"http://127.0.0.1:6969/"}],"passHostHeader":null}}}},"tcp":{},"udp":{},"tls":{}}  providerName=file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traefik started with the static config we provided and then loaded the dynamic config. Let's make sure the dashboard is up. By default Traefik starts the dashboard on port &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;8080&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see this page&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes76re0lhswm66vi7llp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes76re0lhswm66vi7llp.png" alt="image" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traefik is up and running. If we go to &lt;code&gt;/dashboard/#/http/routers/mtls-tut@file&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qd0qh8qd3e32911v6bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qd0qh8qd3e32911v6bn.png" alt="image" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can confirm that our desired configurations were picked up by Traefik. Note the empty &lt;strong&gt;TLS&lt;/strong&gt; and &lt;strong&gt;Middlewares&lt;/strong&gt; sections. We'll be filling those soon.&lt;/p&gt;

&lt;p&gt;The final piece of this first part of the puzzle is modifying our &lt;code&gt;/etc/hosts&lt;/code&gt; file and adding a new entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1 server.mtls-tut.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try curl again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; http://server.mtls-talk.local:8089/headers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we are now accessing the http entrypoint of our reverse proxy and not our backend. Your response should look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Real-Ip: 127.0.0.1
Accept-Encoding: gzip
Heyo: mayo
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: server.mtls-tut.local:8089
X-Forwarded-Port: 8089
X-Forwarded-Proto: http
User-Agent: curl/7.64.1
Accept: */*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All those extra headers are telling us that Traefik is successfully routing our request to our backend at port &lt;a href="https://camo.githubusercontent.com/42cef8be0203e843fc1c25b4be92bb6314721e180e3c23cfd1019387d2de628e/687474703a2f2f69302e6b796d2d63646e2e636f6d2f70686f746f732f696d616765732f6f726967696e616c2f3030302f3435302f3135342f3832302e6a706567" rel="noopener noreferrer"&gt;6969&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Phew! All that work and we haven't even started on the TLS part of things.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLS Time
&lt;/h2&gt;

&lt;p&gt;First we're going to need a CA. You &lt;em&gt;could&lt;/em&gt; go to the trouble of getting a certificate online. If you have two domain names you own, getting certificates from &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt; is an inexpensive solution. But for our tutorial we'll stick with our local resources. Let's make a CA!&lt;/p&gt;

&lt;h3&gt;
  
  
  Break Open that Bottle of openssl
&lt;/h3&gt;

&lt;p&gt;Making your own CA is &lt;a href="https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309" rel="noopener noreferrer"&gt;surprisingly simple&lt;/a&gt; (getting others to trust it is a different matter). You'll need &lt;a href="https://github.com/openssl/openssl" rel="noopener noreferrer"&gt;openssl&lt;/a&gt; which &lt;em&gt;should&lt;/em&gt; already be installed on your platform of choice but if it isn't, google is your friend, again.&lt;/p&gt;

&lt;p&gt;Once you have it, it's time to make the most important decision so far: what are you going to call your CA? I'll keep things simple for myself and call mine &lt;code&gt;mtls-tut.ca&lt;/code&gt;. Not very imaginative but it's been a &lt;em&gt;long&lt;/em&gt; week. &lt;/p&gt;

&lt;p&gt;The first thing we need to do now that we've decided a name is to create the &lt;em&gt;&lt;a href="https://www.thesslstore.com/blog/root-certificates-intermediate/" rel="noopener noreferrer"&gt;Root&lt;/a&gt; &lt;a href="https://www.kinamo.be/en/support/faq/what-are-root-and-intermediate-ssl-certificates" rel="noopener noreferrer"&gt;Certificate&lt;/a&gt;&lt;/em&gt;. The &lt;em&gt;Root Certificate&lt;/em&gt; is a &lt;strong&gt;Big Deal™&lt;/strong&gt;. This is the certificate that will be acting as-- you guessed it-- the root of all certificates issued by our CA. Other certificates will draw their authority &lt;em&gt;from&lt;/em&gt; this Root Certificate.&lt;/p&gt;

&lt;p&gt;First, we need a private key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl genrsa &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; mtls-tut.ca.key 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://sectigo.com/resource-library/public-key-vs-private-key" rel="noopener noreferrer"&gt;private key&lt;/a&gt; is basically a really big random number. We need to keep this number secret. Most CAs don't even keep these keys online. If it can't be accessed via a network it (probably) can't be hacked. CAs take the security of their private keys &lt;em&gt;&lt;a href="https://security.stackexchange.com/questions/24896/how-do-certification-authorities-store-their-private-root-keys" rel="noopener noreferrer"&gt;very seriously&lt;/a&gt;&lt;/em&gt;. In our case, we can choose to be somewhat lackadaisical about the security of our private key. I'm going to store it in a directory called &lt;code&gt;mtls-tut&lt;/code&gt; and pretend it is guarded by digital pitbulls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5138570%2F110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F5138570%2F110125634-d32d7b00-7dc3-11eb-9c2f-c8f27adb255d.jpg" alt="timothy-perry-G9_4owLR9b4-unsplash (1)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you ran the above command you should see output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Generating RSA private key, 4096 bit long modulus
..............................................................................................................++
.........................++
e is 65537 &lt;span class="o"&gt;(&lt;/span&gt;0x10001&lt;span class="o"&gt;)&lt;/span&gt;

❯ &lt;span class="nb"&gt;ls
&lt;/span&gt;mtls-tut.ca.key
~/mtls-tut ❯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a key in hand we need to derive a &lt;a href="https://blog.keyfactor.com/what-is-x509-certificate" rel="noopener noreferrer"&gt;X509 certificate&lt;/a&gt;. I will let you read up on what the X509 format looks like. For our purposes we need to know that the certificate is a collection of fields such as &lt;em&gt;country&lt;/em&gt;, &lt;em&gt;city&lt;/em&gt;, &lt;em&gt;region&lt;/em&gt;, some others, and most importantly, the &lt;em&gt;CommonName&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-key&lt;/span&gt; mtls-tut.ca.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 1024 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; mtls-talk.ca.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get an interactive prompt asking you to enter values for the fields I mentioned above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:BE
State or Province Name (full name) []:Flanders
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:mtls-tut.ca
Email Address []:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of it is informational except the &lt;em&gt;CommonName&lt;/em&gt; which is &lt;em&gt;very important&lt;/em&gt;. Get that one right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ ll
-rw-r--r-- mtls-talk.ca.crt
-rw-r--r-- mtls-tut.ca.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a &lt;em&gt;private key&lt;/em&gt; and a &lt;em&gt;X509 certificate&lt;/em&gt; for our CA. And that's enough to get us started on the next step: creating the server-side certificate. The process is &lt;em&gt;very similar&lt;/em&gt; to what we did for the CA certificate with one key difference. &lt;/p&gt;

&lt;p&gt;We'll start the same as before by generating a &lt;em&gt;private key&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; server.mtls-tut.local.key 2048
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we're going to derive a &lt;em&gt;X509 certificate&lt;/em&gt; from this private key, &lt;em&gt;however&lt;/em&gt;, we will also sign this certificate with the CA key and CA cert we generated earlier. &lt;/p&gt;

&lt;p&gt;To do this we first need a &lt;a href="https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html" rel="noopener noreferrer"&gt;CSR (Certificate Signing Request)&lt;/a&gt;. A CSR is a &lt;em&gt;request&lt;/em&gt; that is signed by the private key of the server and submitted to the CA. If the CA is happy with it, they will issue a &lt;em&gt;certificate&lt;/em&gt; based on this CSR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; server.mtls-tut.local.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=BE/ST=AN/CN=server.mtls-tut.local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; server.mtls-tut.local.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSR includes the same fields we used to generate the certificate of our CA (I decided to include it as part of the command, this time, instead of starting an interactive session). In essence they serve the same function: identification of an entity, be it a server, organization, or other. In the CA's case there isn't any higher authority to authenticate it. It &lt;em&gt;is&lt;/em&gt; the highest digital authority. For a server certificate, the CA is the higher authority that authenticates the server certificate. Since we have our own CA, we can sign our own certificate, giving it our stamp of approval.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; server.mtls-tut.local.csr &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CA&lt;/span&gt; mtls-talk.ca.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; mtls-tut.ca.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; server.mtls-tut.local.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-days&lt;/span&gt; 500 &lt;span class="nt"&gt;-sha256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we break down the command a bit, we'll see that we're taking the CSR as an input, accepting the CA key and cert, and apart from a few config options, outputting a &lt;em&gt;X509 certificate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Phew again! We have our CA and we managed to get some server certificates out of it. Can we see some TLS action already?!&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it All Together
&lt;/h2&gt;

&lt;p&gt;Now we're going to talk to our backend via our reverse proxy using &lt;em&gt;https&lt;/em&gt;. Instead of making the request to &lt;a href="http://server.mtls-tut.local:8089/headers" rel="noopener noreferrer"&gt;http://server.mtls-tut.local:8089/headers&lt;/a&gt; we'll be making it to &lt;a href="https://server.mtls-tut.local/headers" rel="noopener noreferrer"&gt;https://server.mtls-tut.local/headers&lt;/a&gt; (the 443 is implied). If we try it right now...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers
curl: &lt;span class="o"&gt;(&lt;/span&gt;60&lt;span class="o"&gt;)&lt;/span&gt; SSL certificate problem: unable to get &lt;span class="nb"&gt;local &lt;/span&gt;issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...curl is going to complain. If you visit the address in your browser you'll see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxkka0et038mx1em1grf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxkka0et038mx1em1grf.png" alt="image" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we go ahead and &lt;a href="https://www.howtogeek.com/292076/how-do-you-view-ssl-certificate-details-in-google-chrome/" rel="noopener noreferrer"&gt;inspect the certificate&lt;/a&gt; that's being returned to the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre30qjx5e5b0l9tsx7a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre30qjx5e5b0l9tsx7a3.png" alt="image" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey, that's not right! We went to all that trouble to generate a server-side certificate but Traeifk isn't even using it! What gives?&lt;/p&gt;

&lt;p&gt;Well, we give. I think. Never really explored that expression. We haven't configured Traefik to use our certificates, yet. Let's open up the dynamic configuration and add a few lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[http]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.traeifk-api]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`traefik.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api@internal"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`server.mtls-tut.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mtls-tut"&lt;/span&gt;

&lt;span class="nn"&gt;[http.services]&lt;/span&gt;
&lt;span class="nn"&gt;[http.services.mtls-tut.loadBalancer]&lt;/span&gt;
&lt;span class="nn"&gt;[[http.services.mtls-tut.loadBalancer.servers]]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:6969/"&lt;/span&gt;

&lt;span class="nn"&gt;[tls]&lt;/span&gt;
&lt;span class="nn"&gt;[[tls.certificates]]&lt;/span&gt;
&lt;span class="py"&gt;certFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.crt"&lt;/span&gt;
&lt;span class="py"&gt;keyFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added our server cert and key to Traeifk's TLS Certificate store. Now Traefik won't have to rely on its default certificate and can serve up ours, instead.&lt;/p&gt;

&lt;p&gt;Now let's try curling again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers
curl: &lt;span class="o"&gt;(&lt;/span&gt;60&lt;span class="o"&gt;)&lt;/span&gt; SSL certificate problem: unable to get &lt;span class="nb"&gt;local &lt;/span&gt;issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dang it! It still doesn't work. Let's take a look at the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23rhf97juauv438sp0lr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23rhf97juauv438sp0lr.png" alt="image" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alright, at least it's returning the right certificate (and not some default ones generated by Traefik), but the browser and curl both still complain. Why?&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate Stores
&lt;/h3&gt;

&lt;p&gt;Each install of an OS (Operating System) comes bundled with a store of Root Certificates. Remember that Root Certificate we generated a few paragraphs earlier that identified our CA and had all those fields about location and name? Every CA has Root Certificates that they distribute (not the private key, mind you, &lt;em&gt;never&lt;/em&gt; the private key) once the CA has been deemed trustworthy. OSes and even browsers can choose to include these Root Certificates in their Certificate Store.&lt;/p&gt;

&lt;p&gt;What this means is that when a certificate issued by one of these CAs to a server makes its way to a browser, the browser verifies if one of the Root Certificates it holds in its Certificate Store has signed this certificate. If that's the case, the browser knows the certificate can be trusted. Similarly curl relies on the OSes certificate store (it is entirely possible for your browser and your OS to have different Certificate Stores, though &lt;em&gt;most&lt;/em&gt;, if not all, of the Root Certificates in their stores will be identical) to figure out the validity of a server certificate.&lt;/p&gt;

&lt;p&gt;In our case our CA that we made five whole minutes back isn't known to either our browser or to curl.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trust
&lt;/h3&gt;

&lt;p&gt;One way to remedy that is by adding our CA's Root Certificate to these stores, but that's drastic. There's a reason those stores only have trusted Root Certificates in them and you shouldn't mess around with that.&lt;/p&gt;

&lt;p&gt;What we can do instead is to tell curl to trust our CA for the duration of a specific request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;--cacert&lt;/span&gt; /path/to/mtls-tut/mtls-talk.ca.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers

Accept-Encoding: &lt;span class="nb"&gt;gzip
&lt;/span&gt;Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
X-Forwarded-Proto: https
X-Real-Ip: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: server.mtls-tut.local
X-Forwarded-Port: 443
User-Agent: curl/7.64.1
Heyo: mayo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voila! Now the certificate returned by our server is trusted because we have explicitly told curl which Root Certificate to trust as the higher authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally, mTLS!
&lt;/h2&gt;

&lt;p&gt;We're in the final stretch, only a little bit more, I promise.&lt;/p&gt;

&lt;p&gt;Let's finally add mTLS to our reverse proxy. We start by updating our Traefik dynamic config, again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[http]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.traeifk-api]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`traefik.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api@internal"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`server.mtls-tut.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mtls-tut"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut.tls]&lt;/span&gt;
&lt;span class="py"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"myTLSOptions"&lt;/span&gt;

&lt;span class="nn"&gt;[http.services]&lt;/span&gt;
&lt;span class="nn"&gt;[http.services.mtls-tut.loadBalancer]&lt;/span&gt;
&lt;span class="nn"&gt;[[http.services.mtls-tut.loadBalancer.servers]]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:6969/"&lt;/span&gt;

&lt;span class="nn"&gt;[tls]&lt;/span&gt;
&lt;span class="nn"&gt;[[tls.certificates]]&lt;/span&gt;
&lt;span class="py"&gt;certFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.crt"&lt;/span&gt;
&lt;span class="py"&gt;keyFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.key"&lt;/span&gt;

&lt;span class="nn"&gt;[tls.options]&lt;/span&gt;
&lt;span class="nn"&gt;[tls.options.myTLSOptions]&lt;/span&gt;
&lt;span class="nn"&gt;[tls.options.myTLSOptions.clientAuth]&lt;/span&gt;
&lt;span class="py"&gt;clientAuthType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"RequireAnyClientCert"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time not only did we add a new section for configuring client-auth, we also applied that config to our router. Now our router should require &lt;em&gt;some&lt;/em&gt; client certificate to be present. And if we try curling...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;--cacert&lt;/span&gt; mtls-talk.ca.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers

curl: &lt;span class="o"&gt;(&lt;/span&gt;35&lt;span class="o"&gt;)&lt;/span&gt; error:1401E412:SSL routines:CONNECT_CR_FINISHED:sslv3 alert bad certificate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... we see that it is so. The server won't let us in unless we provide a client certificate. So let's generate one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Same Old Song and Dance
&lt;/h3&gt;

&lt;p&gt;The process of generating a client-side certificate is &lt;em&gt;identical&lt;/em&gt; to creating a server-side one. Remember how we made a private key for the server, made a CSR out of it, then got the CA to sign that CSR giving us the final cert? We're going to repeat the exact same steps, only this time changing the &lt;em&gt;CommonName&lt;/em&gt; to &lt;code&gt;client.mtls-tut.local&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; client.mtls-tut.local.key 2048
Generating RSA private key, 2048 bit long modulus
............................+++
..................+++
e is 65537 &lt;span class="o"&gt;(&lt;/span&gt;0x10001&lt;span class="o"&gt;)&lt;/span&gt;

❯ openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; client.mtls-tut.local.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=BE/ST=AN/CN=client.mtls-tut.local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; client.mtls-tut.local.csr

❯ openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; client.mtls-tut.local.csr &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CA&lt;/span&gt; mtls-talk.ca.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; mtls-tut.ca.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; client.mtls-tut.local.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-days&lt;/span&gt; 500 &lt;span class="nt"&gt;-sha256&lt;/span&gt;
Signature ok
&lt;span class="nv"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/C&lt;span class="o"&gt;=&lt;/span&gt;BE/ST&lt;span class="o"&gt;=&lt;/span&gt;AN/CN&lt;span class="o"&gt;=&lt;/span&gt;client.mtls-tut.local
Getting CA Private Key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all went well, we should have 3 new files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ ll
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; client.mtls-tut.local.crt
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; client.mtls-tut.local.csr
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; client.mtls-tut.local.key
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; mtls-talk.ca.crt
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; mtls-talk.srl
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; mtls-tut.ca.key
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; server.mtls-tut.local.crt
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; server.mtls-tut.local.csr
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; server.mtls-tut.local.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! Now we need to ask curl to &lt;em&gt;use&lt;/em&gt; these files when making a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;--key&lt;/span&gt; client.mtls-tut.local.key &lt;span class="nt"&gt;--cert&lt;/span&gt; client.mtls-tut.local.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cacert&lt;/span&gt; mtls-talk.ca.crt &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers

Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
X-Forwarded-For: 127.0.0.1
X-Forwarded-Port: 443
Accept-Encoding: &lt;span class="nb"&gt;gzip
&lt;/span&gt;User-Agent: curl/7.64.1
Heyo: mayo
X-Forwarded-Host: server.mtls-tut.local
X-Forwarded-Proto: https
X-Real-Ip: 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have successfully implemented mTLS! Break out da champagne...&lt;/p&gt;

&lt;p&gt;...later.&lt;/p&gt;

&lt;p&gt;We still have one last thing to do (I &lt;em&gt;promise&lt;/em&gt;, this is the last bit).&lt;/p&gt;

&lt;h3&gt;
  
  
  Trust, Again
&lt;/h3&gt;

&lt;p&gt;Eagle-eyed readers will have noticed that our Traefik configuration is trusting any certificate that we send. For curl to work we had to tell it exactly from which CA to expect the server certifcate. But our Traefik configuration is requiring &lt;em&gt;any&lt;/em&gt; client cert. It's in the name &lt;code&gt;RequireAnyClientCert&lt;/code&gt;. The problem with this being how easy it is to create your own CA and generate certificates. We just did it in a few minutes.&lt;/p&gt;

&lt;p&gt;So in order to restrict Traefik to trusting only &lt;em&gt;specific&lt;/em&gt; CAs we have to submit those Root Certificates into Traeifk's trust store and set the value of &lt;code&gt;clientAuthType&lt;/code&gt; to &lt;code&gt;RequireAndVerifyClientCert&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[http]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers]&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.traeifk-api]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`traefik.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api@internal"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut]&lt;/span&gt;
&lt;span class="py"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Host(`server.mtls-tut.local`)"&lt;/span&gt;
&lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mtls-tut"&lt;/span&gt;

&lt;span class="nn"&gt;[http.routers.mtls-tut.tls]&lt;/span&gt;
&lt;span class="py"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"myTLSOptions"&lt;/span&gt;

&lt;span class="nn"&gt;[http.services]&lt;/span&gt;
&lt;span class="nn"&gt;[http.services.mtls-tut.loadBalancer]&lt;/span&gt;
&lt;span class="nn"&gt;[[http.services.mtls-tut.loadBalancer.servers]]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://127.0.0.1:6969/"&lt;/span&gt;

&lt;span class="nn"&gt;[tls]&lt;/span&gt;
&lt;span class="nn"&gt;[[tls.certificates]]&lt;/span&gt;
&lt;span class="py"&gt;certFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.crt"&lt;/span&gt;
&lt;span class="py"&gt;keyFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/path/to/mtls-tut/server.mtls-tut.local.key"&lt;/span&gt;

&lt;span class="nn"&gt;[tls.options]&lt;/span&gt;
&lt;span class="nn"&gt;[tls.options.myTLSOptions]&lt;/span&gt;
&lt;span class="nn"&gt;[tls.options.myTLSOptions.clientAuth]&lt;/span&gt;
&lt;span class="py"&gt;clientAuthType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"RequireAndVerifyClientCert"&lt;/span&gt;
&lt;span class="py"&gt;caFiles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"/path/to/mtls-tut/mtls-tut.ca.crt"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you curl again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ curl &lt;span class="nt"&gt;--key&lt;/span&gt; client.mtls-tut.local.key &lt;span class="nt"&gt;--cert&lt;/span&gt; client.mtls-tut.local.crt &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cacert&lt;/span&gt; mtls-talk.ca.crt &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"heyo: mayo"&lt;/span&gt; https://server.mtls-tut.local/headers

Accept-Encoding: &lt;span class="nb"&gt;gzip
&lt;/span&gt;X-Forwarded-Host: server.mtls-tut.local
X-Real-Ip: 127.0.0.1
Accept: &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;
Heyo: mayo
X-Forwarded-For: 127.0.0.1
X-Forwarded-Port: 443
X-Forwarded-Proto: https
User-Agent: curl/7.64.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well of course it will work! The certificates we're using for our client-auth was generated by the CA we've told Traefik to trust. But if we try to use a certificate generated by a different CA or change our Traefik config to trust a different CA for client-auth, this call would fail.&lt;/p&gt;

&lt;p&gt;Verifying that last claim is left as an exercise for the reader.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
