Getting Annoyed
Finally!
I ran into the reason why CI/CD actually exists. I finally had a service that I wanted to continually deploy! Until the events of today, I was manually pulling changes in my server then rebuilding my website container. I knew that there was a better way of doing this, but it would take a bit of time.
Today I had some, so I got to work. Reading up on GitHub’s docs, I realised that
you can send a POST request to an endpoint anytime a push event happens on
your repository. The question was then “How do I listen for this?”
Well, the answer was to make a web server that listens.
Since I’m here, I might as well use this chance to write the thing in Go. Since it’s known for being good at this kind of stuff anyways.
An Overview of What My Solution Is Doing
- Listen for connections on
jayantgodse.com/webhook(NGINX routes these to the internal go server) (Also please don’t DDOS me, that would be annoying.)- Had to expose
localhostto the NGINX container <- I’m running the Docker service on the host machine. Ceebs containerising it. - Route
/webhooktohost.docker.internal:8090/webhook
- Had to expose
- The server does some auth:
- Check that it’s a POST request
- Check that it’s got the correct header (“X-Hub-Signature-256”).
- Check that my secret matches with the one being sent.
- Run a script to rebuild my site container.
Mandatory Praise to Golang
I’ve been trying to find an exclude to use Golang for a hot minute now. And this seemed to be a perfect use case.
It seems to do what it says it does. It was easy to write. I basically didn’t have to learn any new syntax. It writes (almost) just like Python, with some C concepts, mainly pointers, sprinkled in there.
The language came with everything I needed for this out of the box. There was a built-in web-server. HTTP handling, JSON decoding, IO handling, Logging, and cryptography algorithms all bundled.
Compilations were fast. Almost instantaneous. Sure the project was small, but
there was genuinely no downtime between me typing go run and the server
spinning up
Did I mention the concurrency was literally just typing the word go in front
of my function? I understand it’s the whole selling point of the language, but
it was still cool to see that being the only thing I had to do.
I like it :)
Okay I will confess the way that struct fields are made public by capitalising them is stupid. Should’ve just had a
publickeyword.
Where to from Here?
The one other thing missing from my setup is reporting to myself when the builds fail. GitHub would do this for me with nice emails. So I think I will have to do the same. Stay tuned for a part 3 where I learn to send emails from my server and hook them into this building service.
Bonus: How the Encryption Works for Dummies (Myself)
I have saved my secret to both this server and GitHub. Funnily enough I think it’s just a sha256 scramble of a no-no word.
Whenever GitHub pings me, it sends a header and a payload. To prove itself as
GitHub, it takes the payload and the shared secret that it has and runs them
through the hmac256 algorithm. This algorithm takes any 2 set of bytes and
produces a single 256 character string. It is guaranteed to be deterministic and
must have very little collisions.
On my end I do the same.
hashObj := hmac.New(sha256.New, []byte(secret))
This is telling Go to make a new [H]ash-based [M]essage [A]uthentication [C]ode object. I then add the bytes of the body
hashObj.Write(bodyBytes)
This I then convert back to a string. And compare with the secret that GitHub has sent me.
hmac.Equal([]byte(signature), []byte(expected))
It seems to be recommended to use the
hmac.Equal()since it’s const time compare.
If they both match, that means whoever is sending me the request knows my key :)