Skip to content

Instantly share code, notes, and snippets.

@anandkunal
anandkunal / README.md
Last active February 10, 2023 09:52
AWS SigV4 & SES Walkthrough in Go

AWS SigV4 & SES Walkthrough in Go

A few years ago, I helped a non-profit organization build and deploy a series of web applications and micro-services on top of AWS. One of the services was responsible for sending out email notifications to people via AWS SES.

Back then, I wrote a really simple program in Go that made direct calls to the AWS API. Instead of using an official library, I read the API specification, learned how to authenticate requests, and implemented a fairly trivial SDK. The code was succinct, readable, and worked perfectly for 3 years without any issues.

That is until this past week…

AWS recently updated the specification, we’re now at Signature Version 4 (SigV4), for how API requests must be formed and signed by clients. In this walkthrough, I’ll share the full Go source code (~130 LOC) that I put together to make valid authenticated calls to AWS for SES. You don’t need to be knowledgeable about Go to grok code in this post. In fact, code in this post will translate pretty cleanly to

Keybase proof

I hereby claim:

  • I am anandkunal on github.
  • I am impvka (https://keybase.io/impvka) on keybase.
  • I have a public key ASBjDikMvXd4VPuD27Sj-ADxx-ERd1LLCOe-MMs5OHU2ZQo

To claim this, I am signing this object:

PASS
ok _/Users/ka/rpc_tutorial 0.038s
func TestColdGet(t *testing.T) {
item, _ := c.Get(cacheItem.Key)
if item != nil {
t.Errorf("Cache key should not exist: %s\n", cacheItem.Key)
}
}
func TestPut(t *testing.T) {
_, err := c.Put(cacheItem)
if err != nil {
var (
c *Client
err error
dsn = "localhost:9876"
cacheItem = &CacheItem{Key: "some key", Value: "some value"}
)
func init() {
c, err = NewClient(dsn, time.Millisecond*500)
func (c *Client) Get(key string) (*CacheItem, error) {
var item *CacheItem
err := c.connection.Call("RPC.Get", key, &item)
return item, err
}
func (c *Client) Put(item *CacheItem) (bool, error) {
var added bool
err := c.connection.Call("RPC.Put", item, &added)
return added, err
type (
Client struct {
connection *rpc.Client
}
)
func NewClient(dsn string, timeout time.Duration) (*Client, error) {
connection, err := net.DialTimeout("tcp", dsn, timeout)
if err != nil {
return nil, err
package main
import (
"log"
"net"
"net/rpc"
"runtime"
)
func init() {
func (r *RPC) Stats(skip bool, requests *Requests) error {
*requests = *r.requests
return nil
}
func (r *RPC) Clear(skip bool, ack *bool) error {
r.mu.Lock()
defer r.mu.Unlock()
r.cache = make(map[string]string)
*ack = true
r.requests.Clear++
return nil
}