Software Development Tips and Tricks


An Introduction to the HiFive1 Rev B and RISC-V

Today I’d like to introduce you to a new development board, the HiFive1 Rev B. Equipped with a RISC-V Freedom E310 microcontroller and designed with an Arduino Uno “form factor”, the HiFive1 Rev B is a neat little board that I hope to learn and develop for.

My HiFive1

There is a lot of material out there about RISC-V and how it is going to change the future of CPUs, but what attracted me to it was the notion of a completely open standard Instruction Set Architecture (ISA). That and I think working with new hardware and development environments is cool.

Getting Started

The Getting Started Guide is crucial to read. If you’re anything like me you want to dig in as quickly as possible with as little reading as possible, but trust me, reading the guide first is very useful.

You don’t get anything but the HiFive1 Rev B board if you’ve ordered it from Crowd Supply and will need a trusty USB-A male to USB-micro-B male cable. This connection can be used for both serial communication and power. Of course, if you have only a system with USB-C you’ll need some set of adapters to get to USB micro-B.

For the host platform we will be using a MacBook Pro (Retina, 13-inch, Early 2015) running macOS 10.15 (Catalina). Hopefully if you’re reading this with the intention of working on the HiFive1 with your Macbook Pro you’ll already have the best terminal program ever installed, but if you don’t regular Terminal.app works.

Let’s see our boot screen first:

To see this boot screen you’ll need to use a serial terminal program. macOS is going to present the HiFive as the two USB modem devices in the /dev directory.

The first cu.usbmodem device presented will be the HiFive1, and my suggestion is to open an iTerm and use screen to connect to it. 115200 bps is your speed and the default 8N1 settings should work, so in our case screen /dev/cu.usbmodem0009790151821 115200 is all we had to type in the terminal.

Time to Develop

There are several key pieces of software you’ll need to install on your Mac to begin developing on the HiFive1.

  • a toolchain (i.e., the compiler, assembler, linker, and associated libraries)
  • OpenOCD On-Chip Debugger
  • the Freedom E SDK
  • Segger J-Link OB debugger

We’ll take each in turn.

Installing the Toolchain and OpenOCD

The reference toolchain is a GNU Embedded Toolchain — v2019.08.0 and can be downloaded directly from SiFive. Go to the Boards page, scroll down until you see the section labeled Prebuilt RISC-V GCC Toolchain and Emulator.

Download both the GNU toolchain and OpenOCD packages and untar both into a suitable installation location. I prefer to keep things like this in the /opt directory, so we’ll do the same here in /opt/riscv.

Before going any further ensure that the compiler can run:

You may have received the error message "riscv64-unknown-elf-gcc" cannot be opened because the developer cannot be verified.. Now, this may be controversial, but I don’t hold to my OS telling me what software I can and can’t run, so let’s get rid of that silliness with spctl:

Let’s try again:

Much better.

Get the SDK

We have a HiFive1 board which uses the Freedom E310 chip, thus will want to get the Freedom E SDK. For this I like to keep the SDK in my projects directory.

Note: You must use --recursive here to check out all of the required packages.

Now, let’s compile that first program!

In the top-level directory freedom-e-sdk is a set of Makefiles that make it easy to compile and generate an image suitable for uploading to the HiFive1 board. In this case, PROGRAM is the name of a subdirectory in freedom-e-sdk/software and TARGET is the board you have.

If this is the first time through you’re going to see a bunch of gibbersh about Python, pip3, virtualenv, etc:

And, if you receive an error regarding the C compiler cannot create executables, you need to set your RISCV_PATH environment variable to point to the correct toolchain like export RISCV_PATH=/opt/riscv/riscv64-unknown-elf-gcc-8.3.0-2019.08.0-x86_64-apple-darwin'.

It’s often a good idea to put the various environment variables in a file such as sifive.env and this source the script into your environment.


If everything worked properly you’ll see at the end something like after compiling.

Installing J-Link

Now, we’re going to upload this to our board, but we will need the Segger J-Link OB debugger. If it isn’t installed you’ll see something like this when trying to use the upload target.

To get J-Link head over to the download page and grab the J-Link Software and Documentation pack for macOS.

Install per the instructions, and once that is done you can go back to your SDK directory and type:

If everything is installed correctly and the board is attached you’ll see something like:

And that’s it! If you have a terminal window with a connection to the serial output of the board you’ll see Hello, World!.

Your Own C Program

In the directory freedom-e-sdk there is a software folder that has an example template. We’ll use that to create our own ASCII art banner.

Copy the template with something like cp -R software/empty software/leo-welcome and then edit the software/leo-welcome/Makefile to change the PROGRAM name to leo-welcome. Open main.c in the same directory and replace the contents with:

Compile and upload with make PROGRAM=leo-welcome TARGET=sifive-hifive1-revb upload and behold.

Some Assembly

I’ll be honest, I struggled with this part, but that’s primarily due to the school of hard knocks with piecing together the calling convention for RISC-V. Let’s review a few examples.

Hello World in RISC-V Assembly

Again, start off with the example folder inside freedom-e-sdk with something like

In this case we’re going to write Hello World in assembly so delete software/helloasm/main.c and instead create a file called main.S with the following content.


In RISC-V the a-registers will contain our procedure arguments, the procedure here being C printf. We use la (load address) to bring the address of the hellomsg label into a0 and then call the printf function. All of these, in reality, are pseudoinstructions.

The Makefile in the template will pick up files with a .S extension, so make PROGRAM=helloasm upload will assemble, link, and upload our file to the HiFive1.

Counting Down

Finally, let’s look at a countdown routine with a max and min. Here things are a bit more complicated as we are going to make use of a prologue and epilogue in our routine. The prologue saves the return address on the stack, and the epilogue loads the address back into the ra register such that the instruction ret will branch back to our caller.


We can call this function with C like this:


Compile and upload!

Good References

There are a lot of great references on RISC-V, its instruction set architecture, and so on. I’ve compiled a few of my favorites here.

Closing Thoughts and What’s Next

That wraps it up for this first look at the SiFive HiFive1 Rev B board. Of course, we haven’t even talked about the ESP32-based wireless capabilities of the board, haven’t talked about the Freedom Metal library or any of the stuff that will accelerate our development. Perhaps that’s next!


Working with ARM Assembly

Don’t ask me why I started looking at writing basic ARM assembly routines. Perhaps it’s for the thrill of it, or taking a walk down memory lane. My first assembly language program was for an IBM System/360 using WYLBUR in college.

This post is not a tutorial on assembly language itself, or the ARM processor for that matter. If the phrases mnemonic, register, or branch on not equal are foreign to you, have a look here. I just wanted to write some easy routines and pick up some basics.

Editor’s note: All of the code below is available on GitHub.

We’ll be using a Raspberry Pi 4. You will (obviously) need the GCC toolchain installed, which can be accomplished with sudo apt-get install build-essential.

Let’s save the following in a file named helloworld.s:


Assemble and link the application together with gcc helloworld.s -o helloworld and run it.

It doesn’t get much more straightforward than this, and you’ve learned three new ARM assembly instructions: ldr, mov, and bl. The remaining text are directives to the GNU assembler which we’ll cover in a minute.

The ldr instruction loads some value from memory into a register. This is key with ARM: load instructions load from memory. In the example above we’re loading the address of the beginning of the string into register r0. A technical note: ldr is actually a psuedoinstruction, but let’s gloss over that.

bl branches to the label indicated (and updates the link register), and in our case, there is this magical printf we’re branching to. More on that later.

Finally, mov r0,#0 is positioning our program’s return code (zero) into r0. Check it:

What if we change the mov r0,#0 to mov r0,#0xff? Try it:

Okay, now for something interesting. Let’s count down from 10 to 1 and then print Hello, world!.


Okay, that escalated quickly! One of the reasons assembly language is so much fun. Let’s take a look at what is going on here and add some comments to our code.

There’s a few things to note here. First, let’s talk about the use of r5 and why that register was deliberately chosen. It turns out that when calling routines in assembly you better not use registers that will get trashed by whatever subroutine your calling (r0-r3). printf can use these registers, so we’ll use r5 in our routine.

Now, I will confess, I am not an assembly language expert much less an ARM assembly language expert. Someone may look at the above code and ask why I didn’t use the subtract-and-compare-to-zero instruction (if there is one) or some other technique. If there is a better way to write the above, please let me know!

Counting Up

In the above example we counted down, now let’s count up, and instead of counting from zero to some max, let’s count up from some minimum value to some maximum value. In other words, we’ll step through a sequence of values using an increment of one.


There’s some new syntax here, in particular the ldr rx,[rx]. This syntax is “load the value that is pointed to by the address in the register.” It makes sense in that there is an instruction immediately before it ldr,=min which is load the address identified by the label min. To be clear, the actual value of that label is going to be dependent on the assembler, your application size, and where it gets loaded into memory. Let’s look at an example of that:


Compile and execute this code to see something like Address of x is 0x21028. Then move x to after fmtstr and you will see the address change. What it will change to, again, is highly dependent on a number of factors. Suffice it to say, using ldr with memory addresses loads the address into a register, not the value at the address. That is what we use ldr rx,[rx] for.

Running our countup code indeed counts up from 14 to 28 and if we look at the return code (echo $?) we get 29, the last value that was in r5.

Writing a Procedure

Here is a basic ARM assembly procedure that computes and returns fib(n), the nth element of the Fibonacci sequence. We chose this specifically to demonstrate the use of the stack with push and pop.


What should be noticed here is the use of push and the list of register values we’re going to save onto the stack. In ARM assembly the Procedure Call Standard convention is to save registers r4-r8 if you’re going to work with them in your subroutine. In the above example we use r4 and r5 to compute fib(n) so we first push r4 and r5 along with the link register. Before returning we pop the previous values off the stack back into the registers.

To use this routine in C we can write:


Then, compile, assemble, and link with gcc fibmain.c fib.s -o fibmain. Recall the procedure call standard convention that the arguments to the procedure will be in r0-r3, hence why our first instruction mov r4,r0 to capture what n we’re calculating the Fibonacci number of.

Taking the Average

Okay, one last routine. We want to take the average of an array of integers. In C that would look something like this:

Here’s a go at it in ARM assembly.


There are some new instructions, an interesting form of the ldr instruction, and a new type of register.

First, the vmov instruction and register s1. vmov moves values into registers of the Vector Floating-Point Coprocessor, assuming your processor has one (if it is a Pi it will). s1 is one of the single-precision floating-point registers. Note that this is a 32-bit wide register that can store a C float.

Next up is the ldr r5,[r0],#4 instruction. Recall that ldr ra, loads the value stored at the address in rb into ra. The #4 at the end instructs the processor to then increment the value in rb by 4. In effect we are walking the array of integers whose starting address is in r0.

Finally, once we add all of the values in the array we have the sum in the register r4. To divide that sum by the length of the array (which was saved off in the floating-point register s1) we load r4 into s0 and perform one last thing: vcvt. vcvt converts between integers and floating-point numbers (which are, after all, an encoding). So s0 gets converted to a floating-point value, as does s1, and then we perform our division with vdiv.

As with r0 being the standard for returning an int from a procedure call, s0 will hold our float value.

We can use this function in our main routine.


Compile with gcc averagemain.c average.s -o averagemain and run.

Closing Thoughts

This post has been a lot of fun to write because assembly is actually fun to write and serves as a reminder that even the highest-level languages get compiled down to instructions that the underlying CPU can execute. One instruction set we didn’t touch on is the store instructions. These are used to save the contents (store) of registers to memory. Perhaps next time.

Once again, all of the code in this post can be found in the armassembly repository on GitHub.


Seriously Good Coffee Doing Serious Good

In the fall of 2018 my CEO and friend David Michel sat down next to me at our corporate Thanksgiving lunch. “Joe, let me show you something,” he said as he pulled out his iPhone. Swiping left and headed for the Photos app he leaned in and exclaimed, “I’m thinking about starting a coffee company.”

I admit that I was confused. “What?” I said, “are you bored here?” “No, no!” he said, and then started to tell me about the story of Hogar Metodista in Costa Rica. Founded by Ray and Lidia Zirkel, the mission of the home is to provide a refuge for Costa Rican children who have been abandoned and need a safe, loving, Christian environment to call their own. The idea behind starting the coffee company was to begin building a sustainable source of income to support that mission.

A week later I e-mailed David writing, “If you need some help, and will have me, I’d love to be a part of this mission.” An hour later he replies, “Just yesterday, I was thinking that I’d love to have you involved.” So involved I became and together with David and others we built Seriously Good Coffee.

Every cent of the net proceeds from every bag (at least $3 per bag) goes to support a Children’s Home in Costa Rica called Hogar Metodista.

There is no one on the Seriously Good Coffee payroll and we work tirelessly to maximize the amount of money that goes to the children’s home. So, if you are interested in not only enjoying a cup of seriously good coffee, but doing some serious good while you’re at it, come visit our Shop page!


Classic Christmas Cheeseball

A holiday classic in these here parts, I give you a Christmas Cheeseball. Yes, you can make this any time of the year, but if you’re counting calories, you may want to make it only during the Christmas season. As I’ve said before, this blog is not going to teach you how to cook, so if you can’t figure out how to turn these ingredients into a cheeseball, well, you need help.

You’re going to need:

  • 2 cups shredded extra sharp cheddar cheese
  • 1/2 pound Velveeta, “shredded”
  • 2 cloves fresh garlic, minced
  • 1 teaspoon Tabasco sauce
  • 1 teaspoon Cajun seasoning
  • 1 teaspoon coarse ground black pepper

The above makes up your cheeseball, and you’re going to roll it in:

  • Chili powder
  • Chopped roasted pecans

In a bowl combine your shredded cheddar cheese and Velveeta. If you’re ever tried to shred Velveeta, well, you know as well as I do that it’s more of a smear, so just treat it like cream cheese. The idea is to combine Velveeta and cheddar cheese together. I prefer extra sharp cheddar, but if you are a wimp (or your guests are) use sharp or mild.

Editor’s note: If you are from foreign lands that have not yet been blessed with Velveeta, look for any locally available processed cheese. For my Uruguayan friends, seek out Claldy Gambú.

Now, add your minced garlic, and don’t be afraid to use minced garlic from a jar or from a tube – the idea is to give this a garlicky kick and the cheeseball doesn’t care if it came from a tube, trust me. Add your Tabasco or whatever insanity sauce you want, along with a “Cajun seasoning”. All of the following are respectable:

Round things out with some black pepper and mold into a ball.

Then, grab your favorite chili powder and dust the ball with it making sure its nicely covered. Chop some roasted pecans and roll your cheeseball in them, taking liberty to “press” the pecans in a bit.

You should end up with something that looks a bit like this:

This is best served a day later (stored in the refrigerator of course), but can be eaten right away.

To serve, lay out a plate of crackers (not saltines, idiot). My favorites are:

  • the venerable Nabisco Sociables
  • the ever popular Club Cracker
  • a plain Triscuit (don’t bother with the flavored Triscuits, they are crap and will just interfere with the cheeseball flavors)
  • Chickin in a Biskit

or just a plain Ritz.



TLS 1.3 with NGINX and Ubuntu 18.04 LTS

OpenSSL 1.1.1 is now available to Ubuntu 18.04 LTS with the release of 18.04.3. This porting of OpenSSL 1.1.1 has opened up the ability to run with TLS 1.3 on your Ubuntu 18.04 LTS NGINX-powered webserver. To add TLS 1.3 support to your existing NGINX installation, first upgrade your Ubuntu 18.04 LTS server to 18.04.3, and then find the ssl_protocols directive for NGINX and add TLSv1.3 at the end:

Restart NGINX with systemctl restart nginx.

It really is as simple as that! If your browser supports TLS 1.3 (and all major browsers do as of November 2019 with the notable exception of Microsoft Edge) it will negotiate to it. As of this writing (November 2019), you would not want to disable TLSv1.2. Odds are you will break tools such as cURL and other HTTPS agents accessing your site. Here’s an example of what that looks like for curl on macOS 10.14.6 (Mojave):

In other words, the stock macOS 10.14.6 curl cannot establish a connection with a webserver running only TLS 1.3.

Enabling 0-RTT

There are a lot of compelling features to TLS 1.3, one of them being 0-RTT for performance gains in establishing a connection to the webserver. NGINX enables TLS 1.3 0-RTT if the configuration parameter ssl_early_data is set to on. If you are using the stock NGINX provided by Ubuntu 18.04 LTS 0-RTT is not supported. Let’s upgrade to the version provided by the NGINX PPA and enable it.

Go back to your NGINX configuration and place the ssl_early_data directive near all of the other ssl_ directives, like this:

Now, all that being said, 0-RTT is not something you will want to enable without careful consideration. The “early” in SSL early data comes from the idea that if the client already has a pre-shared key, it can reuse the key. This is a great post outlining the benefits, and risks, of enabling 0-RTT.


OpenWrt SNMP Interface Descriptions

If you’re familiar with configuring network gear, you know that a very useful best practice is providing “plain English” descriptions of your device’s ports. For example, on my Cisco SF500-48MP switch port 24 is the “uplink port” to the gateway router. I make this clear in the port’s description:

sw01#show interfaces description fa1/2/24
Port      Description
-------   -----------
fa1/2/24  Uplink to Internet Gateway

By doing so, the ifAlias OID for this interface is set:

snmpget -c public -v2c sw01.iachieved.it IF-MIB::ifAlias.24 
IF-MIB::ifAlias.24 = STRING: Uplink to Internet Gateway

What is particularly nice about this is that a network monitoring tool such as Observium will display the ifAlias string as a part of the description of the port. Like I said, this becomes very useful, particularly when trying to track down where ports lead to.

In the previous post we installed SNMP on an OpenWrt router and surfaced it in Observium. By default the snmpd package doesn’t present any information for ifAlias, but we can fix that with snmpset.

Permitting snmpset Access

snmpset will make use of the SNMP private community on our OpenWrt (note: if you were working in a production environment you might consider using SNMP v3 with authentication or at the very least changing your community strings). By default the OpenWrt SNMP configuration only permits use of the private community from localhost (i.e., the router itself). We’ll change that to permit access from our private subnet:

Find this section in your /etc/config/snmpd file

config com2sec private
    option secname rw
    option source localhost
    option community private

and change the option source like this:

option source

Obviously you’ll use the appropriate subnet in your configuration.

Restart snmpd on the router with /etc/init.d/snmpd restart.

Updating ifAlias

To update the appropriate ifAlias entries we need to see the ifDescr list. This can be obtained by walking ifDescr with snmpwalk:

snmpwalk  -c public -v2c gw.gw01.chcgil01.iachieved.it ifDescr
IF-MIB::ifDescr.1 = STRING: lo
IF-MIB::ifDescr.2 = STRING: eth1
IF-MIB::ifDescr.3 = STRING: eth0
IF-MIB::ifDescr.5 = STRING: wlan0
IF-MIB::ifDescr.6 = STRING: wlan1
IF-MIB::ifDescr.7 = STRING: br-lan
IF-MIB::ifDescr.8 = STRING: eth0.1
IF-MIB::ifDescr.9 = STRING: eth1.2
IF-MIB::ifDescr.10 = STRING: eth0.100
IF-MIB::ifDescr.11 = STRING: eth1.3
IF-MIB::ifDescr.12 = STRING: eth1.4

In our Chicago router example let’s label the three interfaces that are OSPF links to other routers:

  • eth1.2 is a link to gw01.dnvrco01
  • eth1.3 is a link to gw01.atlaga01
  • eth1.4 is a link to gw01.dllstx01

From the output of ifDescr we can see that

  • eth1.2 will map to ifAlias.9
  • eth1.3 will map to ifAlias.11
  • eth1.4 will map to ifAlias.12

So let’s set those ifAlias strings!

# snmpset -c private -v2c gw.gw01.chcgil01.iachieved.it ifAlias.9 string "OSPF Link to gw01.dnvrco01"
IF-MIB::ifAlias.9 = STRING: OSPF Link to gw01.dnvrco01
# snmpset -c private -v2c gw.gw01.chcgil01.iachieved.it ifAlias.11 string "OSPF Link to gw01.atlaga01"
IF-MIB::ifAlias.11 = STRING: OSPF Link to gw01.atlaga01
# snmpset -c private -v2c gw.gw01.chcgil01.iachieved.it ifAlias.12 string "OSPF Link to gw01.dllstx01"
IF-MIB::ifAlias.12 = STRING: OSPF Link to gw01.dllstx01

The Catch

The problem with this approach is its persistence – reboot your router and watch those interface descriptions bite the dust. But no worries, the fix is simple.

Go back to /etc/config/snmpd and change your private community to accept interaction from localhost (in other words, what it was originally!):

config com2sec private
    option secname rw
    option source localhost
    option community private

Restart snmpd with /etc/init.d/snmpd restart.

On the router we’re going to edit /etc/rc.local and before exit 0 put:

# Wait for snmpd to accept connections
/bin/sleep 5

/usr/bin/snmpset -c private -v2c localhost ifAlias.9 string "OSPF Link to gw01.dnvrco01" > /tmp/snmpset.log
/usr/bin/snmpset -c private -v2c localhost ifAlias.11 string "OSPF Link to gw01.atlaga01" >> /tmp/snmpset.log
/usr/bin/snmpset -c private -v2c localhost ifAlias.12 string "OSPF Link to gw01.dllstx01" >> /tmp/snmpset.log

I have not optimized the /bin/sleep at this point, but without it snmpset will be talking to an snmpd daemon that isn’t ready. Trust me.

You can now reboot the router and the custom interface descriptions will survive.

Wrapping Up

Why did we go to all the trouble of creating descriptions (aliases) for our OpenWrt interfaces? Again, monitoring tools such as Observium will take those descriptions and apply them to your UI.

At a glance I can quickly see, for example, that eth1.2 is the interface being used for OSPF with gw01.dnvrco01. That information is incredibly useful when working with dozens (or more) links.


Recognizing OpenWrt as an OS in Observium

Observium is a great application for monitoring network equipment, regardless of type (e.g., routers, switches, firewalls, etc.) What makes it so powerful is due in large part to the amount of information exposed by SNMP for network gear and its ability to intelligently parse the returned data and display it.

This intelligence can only go so far, however, when a given piece of gear has either an incomplete implementation of SNMP or the values returned aren’t indicative of the equipment. Take, for example, OpenWrt. It is, in a word, an awesome piece of software, capable of turning a $250 Linksys home router into a participant in an OSPF area. Pretty nift.

Due to its open nature there are a number of SNMP options for OpenWrt:

Only one of these will give you a suitable view in Observium, and that is the snmpd package. Let’s install it (note that I’m using the OpenWrt shell vs. LUCI):

# opkg update
# opkg install snmpd

Unfortunately if you add your device now Observium will recognize it as a generic Linux machine. That’s due to the fact that, by default, the OpenWrt snmpd package will not return suitable information in the sysDescr OID for Observium’s OS detection routines.

For reference, here is what you can expect Observium to display with snmpd not configured properly:

Let’s take a look directly at the sysDescr OID with snmpget, which is available by installing snmp on your Observium host (if you’re using a Debian variant). There is a little dance to be done to get snmpget to work properly:

# apt-get install snmp snmp-mibs-downloader
# printf "[snmp]\nmibs +ALL\n" > /etc/snmp/snmp.conf 
# download-mibs
# snmpget -v2c -c public <HOSTNAME> sysDescr.0

For our router:

snmpget -v2c -c public gw.gw01.chcgil01.iachieved.it sysDescr.0
. = STRING: Linux chcgil 4.14.131 #0 SMP Thu Jun 27 12:18:52 2019 armv7l

sysDescr as is will cause Observium to detect the router as a basic Linux OS. We want more. Here’s how to do it! Go to the /etc/config/snmpd file in OpenWrt and find this block:

config system                   
        option sysLocation      'office'
        option sysContact       'bofh@example.com'
        option sysName          'HeartOfGold'     
#       option sysServices      72                
#       option sysDescr         'adult playground'
#       option sysObjectID      ''

sysLocation can be set to a locale name and Observium will automatically map it properly. Since this router is in Chicago we’ll put Chicago there. Likewise, sysName will be changed to gw01.chcgil01 as this router is Gateway #1 in Chicago Site #1. What we’re particularly interested in changing here is sysDescr. Uncomment the line and change it to OpenWrt. Here’s what our final config system block looks like:

config system                   
        option sysLocation      'Chicago'
        option sysContact       'admin@iachieved.it'
        option sysName          'gw01.chcgil01'     
        option sysDescr         'OpenWrt'

Restart snmpd:

# /etc/init.d/snmpd restart

And check snmpget again:

# snmpget -v2c -c public gw.gw01.chcgil01.iachieved.it sysDescr.0
SNMPv2-MIB::sysDescr.0 = STRING: OpenWrt

Perfect. Add the device to Observium and watch it fill in the rest.

Notice that the Tux logo has been replaced with the OpenWrt logo as Observium correctly identifies this device as running OpenWrt.


The Best Fudgy Brownies Ever

This is an experimental blog post, because in the sea of posts about Ansible and Swift is a brownie recipe. To be fair, this is not just any brownie recipe, but the best one ever for fudgy brownies.

Why am I writing this post here on iAchieved.it? Because the world needs to know how to make these brownies, and I’m sick of recipe blogs that are ninety percent rambling (we’re not going to go into the history of the brownie here) and instructions on how to fold. If you don’t know to fold versus whisk, this blog isn’t for you. I’m not even going to bother with fancypants WordPress plugins for recipes. Well, not for now at least.

This recipe makes a 13×9 pan of awesome fudgy brownies. I am not joking.

  • 1/3 cup Dutched cocoa powder
  • 2 ounces unsweetened chocolate, chopped
  • 1/2 cup plus 2 tablespoons boiling water
  • 4 tablespoons unsalted butter, melted
  • 1/2 cup plus 2 tablespoons vegetable oil
  • 2 eggs
  • 2 egg yolks
  • 2 teaspoons vanilla
  • 2 1/2 cups sugar
  • 1 3/4 cups unbleached all-purpose flour
  • 3/4 teaspoon salt
  • 6 ounces bittersweet chocolate chips

In a large mixing bowl (the kind you might, you know, make brownies in) toss in the dutched cocoa powder and chopped unsweetened chocolate. Pour in the boiling water and whisk until smooth. If you’re lucky I might add a picture here.

At this point whisk in the melted butter and vegetable oil, but be prepared for it to look a little weird. The chocolate and the oils will not want to get happy together, but don’t worry, that’s when you whisk in the eggs and extra yolks and everyone comes together like a nice chocolate pudding. By all means, shove your finger in there for a taste, but there’s no sugar yet so it’ll be nasty.

Whisk in the vanilla (I grew up in Texas so it goes without saying my preference is Mexican vanilla), and in 1/2 cup batches whisk in the sugar until thoroughly combined and glossy. Note: If you’ve ever done any baking you know that there’s always cautions regarding over mixing. We will now invoke this caution because you’re going to start adding the flour. Do not overmix after this! We’re making brownies, not bread.

Fold in the flour and salt. I usually do this a half cup at a time and don’t fret if there are some streaks of flour in the batter. It’ll sit a bit and hydrate while you go for that second glass of wine, so relax.

Once you’ve folded in all of the flour and it looks like brownie batter, fold in those bittersweet chocolate chips. Fold, not mix. Remember, boxed brownies have a lot of chemicals that act like guard rails. No rails here, so don’t get crazy.

Pour all of the batter into a greased (PAM, people) 13×9 and bake for about 32 to 35 minutes in a 350 degree oven. I usually rotate it after 15 minutes, but that’s only because Memaw did.

Let cool for a bit if you have self-control, but if you’re like me, by all means, tear into there and enjoy molten fudgy brownie goodness. Here’s what is you can expect to indulge in!


Auditing Shared Account Usage

Occasionally you find yourself in a situation where utilizing a shared account cannot be avoided. One such scenario is managing the deployment of NodeJS applications with Shipit and PM2. Here’s how the scenario typically works:

Alice, Bob, and Carol are three developers working on NodeJS applications that need to be deployed to their staging server. They’ve decided on the use of PM2 as their process manager, and ShipIt as their deployment tool. Their shipitfile.js file contains a block for the staging server, and it looks something like:

staging: {
      servers: [
          host: 'apps.staging.iachieved.it',
          user: 'deployer',
      environment:  'staging',
      branch: 'develop',

As we can see the deployer user will be used to deploy our application, and by extension, will be the user that pm2 runs the application under. Alice, Bob, and Carol all have their SSH keys put in /home/deployer/.ssh/authorized_keys so they can deploy. Makes sense.

Unfortunately what this also means is Alice, Bob, or Carol can ssh to the staging server as the deployer user. Even though deployer is an unprivileged user, we really don’t want that. Moreover, by default, we can’t trace who deployed, or if someone is misusing the deployer user. Let’s take a look at how we can address this.

Creating a Deployment Group

The first thing we want to do is to create a security group for those that are authorized to perform deployments. I’ll be using an Active Directory security group in this example, but a standard Unix group would work as well. We can use getent to see the members of the group. getent will come in handy to help determine whether someone attempting to deploy is authorized.

# getent group "application deployment@iachieved.it"
application deployment@iachieved.it:*:1068601118:alice@iachieved.it,bob@iachieved.it

SSH authorized_keys command

Until I started researching this problem of auditing and restricting shared account usage I was unaware of the command option in the SSH authorized_keys file. One learns something new every day. What the command option provides for is executing a command immediately upon SSHing via a given key. Consider that we put the following entry in the deployer user ~/.ssh/authorized_keys file:

ssh-rsa AAAA...sCBR alice

and this is Alice’s public key. We would expect that Alice would be able to ssh deployer@apps.iachieved.it and get a shell. But what if we wanted to intercept this SSH and run a script instead? Let’s try it out:


command="/usr/bin/logger -p auth.INFO Not permitted" ssh-rsa AAAA...sCBR alice

When Alice tries to ssh as the deployer user, we get an entry in auth.log:

Jul  5 22:30:58 apps deployer: Not permitted

and Alice sees Connection to apps closed..

Well that’s no good! We do want Alice to be able to use the deployer account to deploy code.

A Wrapper Script

First, we want Alice to be able to deploy code with the deployer user, but we also want to:

  • know that it was Alice
  • ensure Alice is an authorized deployer
  • not allow Alice to get a shell

Let’s look at how we can create a script to execute each SSH invocation that will meet all of these criteria.

Step 1, let’s log and execute whatever Alice was attempting to do.


SSH_ORIGINAL_COMMAND will be set automatically by sshd, but we need to provide SSH_REMOTE_USER, so in the authorized_keys file:

command="export SSH_REMOTE_USER=alice@iachieved.it;/usr/local/bin/deploy.sh" ssh-rsa AAAA...sCBR alice

Note that we explicitly set SSH_REMOTE_USER to alice@iachieved.it. The takeaway here is that it associates any attempt by Alice to use the deployer account to her userid. We then execute deploy.sh which logs the invocation. If Alice tries to ssh and get a shell with ssh deployer@apps the connection will still be closed, as SSH_ORIGINAL_COMMAND is null. But, let’s say she runs ssh deployer@apps ls /:

alice@iachieved.it@apps ~> ssh deployer@apps ls /

In /var/log/auth.log we see:

Jul  6 13:43:25 apps sshd[18554]: Accepted publickey for deployer from ::1 port 48832 ssh2: RSA SHA256:thZna7v6go5EzcZABkieCmaZzp+6WSlYx37a3uPOMSs
Jul  6 13:43:25 apps sshd[18554]: pam_unix(sshd:session): session opened for user deployer by (uid=0)
Jul  6 13:43:25 apps systemd-logind[945]: New session 54 of user deployer.
Jul  6 13:43:25 apps systemd: pam_unix(systemd-user:session): session opened for user deployer by (uid=0)
Jul  6 13:43:26 apps deployer: alice@iachieved.it executed ls /

What is important here is that we can trace what Alice is executing.

Managing a Deployment Security Group

Left as is this technique is much preferred to a free-for-all with the deployer user, but more can be done using security groups to have finer control of who can use the account at any given time. Let’s add an additional check in the /usr/local/bin/deploy.sh script with the uig function introduced in the last post.

The authorized_keys file gets updated, and let’s add Bob and Carol’s keys for our additional positive (Bob) and negative (Carol) test:

Since Bob is a member of the application deployment@iachieved.it group, he can proceed:

Jul  6 20:09:26 apps sshd[21886]: Accepted publickey for deployer from ::1 port 49148 ssh2: RSA SHA256:gs3j1xHvwJcSMBXxaqag6Pb7A595HVXIz2fMoCX2J/I
Jul  6 20:09:26 apps sshd[21886]: pam_unix(sshd:session): session opened for user deployer by (uid=0)
Jul  6 20:09:26 apps systemd-logind[945]: New session 79 of user deployer.
Jul  6 20:09:26 apps systemd: pam_unix(systemd-user:session): session opened for user deployer by (uid=0)
Jul  6 20:09:27 apps deployer: bob@iachieved.it is in application deployment@iachieved.it and executed ls /

Now, Carol’s turn to try ssh deployer@apps ls /:

Jul  6 20:15:37 apps deployer: carol@iachieved.it is not in application deployment@iachieved.it and is not authorized to execute ls /

Poor Carol.

Closing Thoughts

For some teams the idea of having to manage a deployment security group and bespoke authorized_keys file may be overkill. If you’re in an environment with enhanced audit controls and accountability the ability to implement safeguards and audits to code deployments may be a welcome addition.


A Script for Testing Membership in a Unix Group

Sometimes you just need a boolean test for a given question. In this post we’ll look at answering the question, “Is this user in a given group?” Seems simple enough.

It’s easy to see what groups a user is a member of in a shell:

% id
alice@iachieved.it@darthvader:~$ id
uid=1068601116(alice@iachieved.it) gid=1068601115(iachievedit@iachieved.it) groups=1068601115(iachievedit@iachieved.it),1068600513(domain users@iachieved.it),1068601109(linux ssh@iachieved.it),1068601118(application deployment@iachieved.it)

Note that Alice is an Active Directory domain user. We want to test whether or not she is a member of the application deployment@iachieved.it group. We can see this with our eyes in the terminal, but a little scripting is in order. We’ll skip error checking for this first example.


Let’s take it out for a spin.

alice@iachieved.it@darthvader:~$ ./uig.sh `whoami` "linux administrators@iachieved.it"
User alice@iachieved.it IS NOT in group linux administrators@iachieved.it

Now let’s test whether or not Alice is in the application deployment@iachieved.it group:

alice@iachieved.it@darthvader:~$ ./uig.sh `whoami` "application deployment@iachieved.it"
User alice@iachieved.it IS in group application deployment@iachieved.it

Terrific. This will come in handy in the next blog post.

Let’s clean this up into a function that can be sourced in a script or shell:

alice@iachieved.it@darthvader:~$ uig `whoami` "linux ssh@iachieved.it"
alice@iachieved.it@darthvader:~$ echo $?

Or, the invocation we’re most likely to use: