Today I became a Go developer, with vim and Caddy
Published 2015-6-10Yesterday 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
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )