Skip to content

Instantly share code, notes, and snippets.

Avatar

Kunal Anand anandkunal

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

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

View keybase.md

Keybase proof

I hereby claim:

  • I am anandkunal on github.
  • I am kunalanand (https://keybase.io/kunalanand) on keybase.
  • I have a public key ASBB49TMrvEQLZhvjuWQsz6Poc28_OuAEcKicNB-7AGnSAo

To claim this, I am signing this object:

View rpc_tests.sh
PASS
ok _/Users/ka/rpc_tutorial 0.038s
View rpc_test_bits.go
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 {
View rpc_test_init.go
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)
View client_funcs.go
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
View client_constructor.go
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
View rpc_server.go
package main
import (
"log"
"net"
"net/rpc"
"runtime"
)
func init() {
View rpc_stats.go
func (r *RPC) Stats(skip bool, requests *Requests) error {
*requests = *r.requests
return nil
}
View rpc_clear.go
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
}