Yesterday I was an io.js / node developer. Today I made a giant leap forward into the future.

Today I became a golang developer as well. :-D

Why node isn't good enough

I've been working on a Home Cloud system.

The goal is to have a device that people in India and China can afford, as well as the average high-school student in America - who is paying so much for car insurance that he can barely afford to take a gal on a date on payday.

The current lowball contender is the Raspberry Pi, so that's my current target.

The problem with node is that it is painfully slow on ARM. Don't get me wrong, it's actually quite snappy, but the difference between running it on a Digital Oceean instance and an RPi is night and day.

I wanted to offload HTTPS (the TLS encryption part), LetsEncrypt, static file serving, and perhaps a few other CPU- and garbage-collection-heavy features to something else.

Caddy seduced me

Caddy is the Golang project that finally tipped the bucket for me.

Simply put, it's a more Awesome HTTP server. It's no nginx yet, but it's a rising contender - and it's in a language that I've been wanting to dig into.

And so it was that my first true foray into real go code was sitting down with Caddy's creator and implementing a rather esoteric feature.

go, go, go

OS X

No surprises here

brew install go

Raspberry Pi

This took a little longer than expected. Make sure you don't have any other process that are consuming lots of RAM or CPU when you go for this.

wget https://xivilization.net/~marek/raspbian/xivilization-raspbian.gpg.key -O - | sudo apt-key add -
sudo wget https://xivilization.net/~marek/raspbian/xivilization-raspbian.list -O /etc/apt/sources.list.d/xivilization-raspbian.list

sudo aptitude update
sudo aptitude install --yes golang

# Swapping on RPi is bad news,
# I think it's off by default but it doesn't hurt to double check
sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall

And Bash

You'll need to run these in the current bash shell as well as add them to your ~/.bashrc.

vim ~/.bashrc

export GOPATH="${HOME}/go"
export PATH="${GOPATH}/bin:${PATH}"

And Fish

You'll need to run these in the current fish shell as well as add them to your fish config.

vim ~/.config/fish/config.fish

set -g -x GOPATH "$HOME/go"
set -g -x PATH "$GOPATH/bin" $PATH

And goimports

go get golang.org/x/tools/cmd/goimports

vim, vim, vim

Just a little plugin install and setup (from the top in case you haven't done this before):

# Install pathogen
mkdir -p ~/.vim/autoload ~/.vim/bundle && \
curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim

# Install vim-sensible
git clone git://github.com/tpope/vim-sensible.git ~/.vim/bundle/vim-sensible

# Install syntastic
git clone https://github.com/scrooloose/syntastic.git ~/.vim/bundle/syntastic

# Install vim-go
git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go

And a few edits to your config file:

vim ~/.vimrc

execute pathogen#infect()

let g:go_fmt_command = "goimports"

let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0

And that's about it. There's nothing to fancy to get started.

Note that this will automatically fix missing imports, remove extraneous ones, and fix your whitespace and such to conform to the golang standards (it's a very strict language, thankfully, so the compiler and tooling require it).

caddy, caddy, caddy

You can easily download a binary for Caddy, but the ARM build wasn't correct for my CPU and, hey, I'm just the kind of guy that prefers to build from source anyway if I'm going to be working on the project, y'know?

# this git clones caddy and it's deps into $GOPATH/src
# it will also build it and place the bin in $GOPATH/bin
go get github.com/mholt/caddy
ls -lah ~/go/bin/caddy

Fork to Play

If you go to https://github.com/mholt/caddy and click the fork button, you can update your local repo like so:

vim ~/go/src/github.com/mholt/caddy/.git/config:

[remote "upstream"]
  fetch = +refs/heads/*:refs/remotes/origin/*
  url = https://github.com/mholt/caddy
[remote "origin"]
  fetch = +refs/heads/*:refs/remotes/origin/*
  url = git@github.com:coolaj86/caddy.git
  • I renamed the existing "origin" to "upstream"
  • I created a new "origin" to my own fork

Maybe I should have ran go get github.com:coolaj86/caddy and then added upstream to that, but for now I just plan on making enhancements that I'll be pull-requesting back to the upstream. If I start doing crazy stuff then I'll actually bother to make my local fork really look like a fork.

Test Drive

Just for the sake of having made a change, let's say that we want to add a secret backdoor to the basicauth module (which would never be accepted, btw).

pushd ~/go/src/github.com/mholt/caddy

Right after the normal checks, around line 48 or so we could add this:

vim ./middleware/basicauth/basicauth.go:

  // Add a secret backdoor
  if !isAuthenticated {
    username, password, ok := r.BasicAuth()
    if ok &&
      subtle.ConstantTimeCompare([]byte(username), []byte("aj")) == 1 &&
      subtle.ConstantTimeCompare([]byte(password), []byte("is awesome")) == 1 {
      isAuthenticated = true
    }
  }

And then we could do a red/green test to make sure our backdoor works

vim ./middleware/basicauth/basicauth_test.go:

    // find this line that already exists in the first test function:
    {"/testing", http.StatusOK, "test:ttest"},
    // add this test case, which should fail, right below it
    {"/testing", http.StatusOK, "aj:is not awesome"},

And run the test:

pushd ./middleware/basicauth
# this will run specified files or *_test.go, with no arguments
go test

If all goes well that should fail with Test 2: Expected Header '200' but was '401'.

Then change the line we added earlier to be this line, and run the test again:

    {"/testing", http.StatusOK, "aj:is awesome"},

And then test the change again:

go test

Assuming that failed, we're ready to build and go test it for realz!

# go back up to the main repo to build
popd

# may output to $GOPATH/bin/caddy or ./caddy or ./main
go build

With that all done, we can pop back out of the repo directory if we want.

# now go back home
popd

Real Test Drive

Now let's just serve up our /tmp directory (backdoor included)

vim /tmp/Caddyfile:

0.0.0.0:3000 {
  root /tmp
  gzip
  basicauth reallyhardtoguess neverguessable {
    /
  }
}
echo "Hello, Clarise" > /tmp/creepy.txt
caddy --conf /tmp/Caddyfile

If you open up http://localhost:3000/creepy.txt you should be prompted for a password.

If all goes well you should be able to use the secret backdoor aj and is awesome and see the fun message that awaits you.

Awsome! Thanks for helping me pwn your server. Not only do I have a bridge to sell you, but there's also this rootkit called wordpress that you should totally install on your server as well.

Undoing the Damage

If our backdoor were something other than introducing a security flaw we might want to run something like this:

git checkout -b create-zero-day-exploit
git add middleware/basicauth
git commit -m "add a not-so-secret backdoor password"
git push -u origin create-zero-day-exploit

Then we could go to our github account and make a pull request.

But we won't.

Instead, we'll reset the commit (just to show how that's done) and delete the branch.

pushd $GOPATH/src/github.com/mholt/caddy

# make sure only the basicauth files have been modified
git status

# go back one commit - deleting all files in the way
git reset 'HEAD^' --hard
git checkout master

# attempts a safe branch delete
git branch -d create-zero-day-exploit
# forces a delete
git branch -D create-zero-day-exploit

# rebuild caddy, without the zero-day exploitable backdoor
go build

By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )