package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/url"
"strings"
"time"
"github.com/fastly/compute-sdk-go/edgedict"
"github.com/fastly/compute-sdk-go/fsthttp"
)
const BackendS3 = "origin_0"
const EMPTY_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
type S3Config struct {
access_key_id string
secret_access_key string
region string
host string
}
func (s *S3Config) Set() error {
dict, err := edgedict.Open("s3_config")
if err != nil {
return err
}
access_key_id, err := dict.Get("access_key_id")
if err != nil {
return fmt.Errorf("access_key_id %s", err)
}
s.access_key_id = access_key_id
secret_access_key, err := dict.Get("secret_access_key")
if err != nil {
return fmt.Errorf("secret_access_key %s", err)
}
s.secret_access_key = secret_access_key
region, err := dict.Get("region")
if err != nil {
return fmt.Errorf("region %s", err)
}
s.region = region
host, err := dict.Get("host")
if err != nil {
return fmt.Errorf("host %s", err)
}
s.host = host
return nil
}
func authorize_s3_request(r *fsthttp.Request, s3 S3Config) *fsthttp.Request {
canonical_querystring := ""
x_amz_content_sha256 := EMPTY_HASH
amz_date := time.Now().Format("20060102T150405Z")
path_url_decoded, _ := url.QueryUnescape(r.URL.Path)
path_url_encoded := url.QueryEscape(path_url_decoded)
canonical_uri := strings.Replace(path_url_encoded, "%2F", "/", -1)
authorization_value := generate_s3_authorization_header(s3, canonical_uri, canonical_querystring, x_amz_content_sha256, "GET", amz_date, "s3")
r.Method = "GET"
r.Header.Set("host", s3.host)
r.Header.Set("AUTHORIZATION", authorization_value)
r.Header.Set("x-amz-content-sha256", x_amz_content_sha256)
r.Header.Set("x-amz-date", amz_date)
return r
}
func generate_s3_authorization_header(s3 S3Config, canonical_uri string, canonical_querystring string, x_amz_content_sha256 string, method string, amz_date string, service string) string {
canonical_header := "host:" + s3.host + "\n" + "x-amz-content-sha256:" + x_amz_content_sha256 + "\n" + "x-amz-date:" + amz_date + "\n"
signed_headers := "host;x-amz-content-sha256;x-amz-date"
canonical_request := method + "\n" + canonical_uri + "\n" + canonical_querystring + "\n" + canonical_header + "\n" + signed_headers + "\n" + x_amz_content_sha256
credential_date := amz_date[:8]
credential_scope := credential_date + "/" + s3.region + "/" + service + "/" + "aws4_request"
canonical_request_hash := hex.EncodeToString(getSHA256Binary(canonical_request))
string_to_sign := "AWS4-HMAC-SHA256" + "\n" + amz_date + "\n" + credential_scope + "\n" + canonical_request_hash
aws4_secret_string := "AWS4" + s3.secret_access_key
aws4_secert := []byte(aws4_secret_string)
date_key := sign(aws4_secert, credential_date)
region_key := sign(date_key, s3.region)
service_key := sign(region_key, service)
signing_key := sign(service_key, "aws4_request")
signature := hex.EncodeToString(sign(signing_key, string_to_sign))
return "AWS4-HMAC-SHA256 Credential=" + s3.access_key_id + "/" + credential_scope + ",SignedHeaders=" + signed_headers + ",Signature=" + signature
}
func sign(key []byte, message string) []byte {
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return h.Sum(nil)
}
func main() {
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
var s3 S3Config
err := s3.Set()
if err != nil {
w.WriteHeader(fsthttp.StatusInternalServerError)
fmt.Fprintln(w, err.Error())
return
}
position := strings.Index(r.URL.String(), "?")
new_url := r.URL.String()
if position > 0 {
new_url = new_url[0:position]
}
r.URL, err = url.Parse(new_url)
if err != nil {
w.WriteHeader(fsthttp.StatusInternalServerError)
fmt.Fprintln(w, err.Error())
}
switch r.Method {
case "GET", "HEAD":
r = authorize_s3_request(r, s3)
case "PURGE":
default:
w.WriteHeader(fsthttp.StatusNotFound)
return
}
fmt.Printf("Sending: %v, Path: %v", r.URL, r.URL.Path)
resp, err := r.Send(ctx, BackendS3)
fmt.Println("Sent")
if err != nil {
w.WriteHeader(fsthttp.StatusBadGateway)
fmt.Fprintln(w, err.Error)
return
}
w.Header().Reset(resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
})
}
func getSHA256Binary(s string) []byte {
r := sha256.Sum256([]byte(s))
return r[:]
}