How to create AWS Signature on GET POST request

Signing request with AWS Signature

How to create AWS Signature on GET POST request

Postman, it’s a great tool for debuging, testing and other activities performed by us - developers. It has a build in option to sign requestes with AWS Signature . We do have AWS SDK for Go v2 , but I couldn’t find any good examples except a test file . The other useful file will be const.go where we can find a couple of interesting variables.

Code for making a query look like that (this part is for GET query). Actions happening here:

  1. we create a reader for the body (for POST requests)
  2. we buildRequest which enriches our request with some extra headers
  3. we send our request via standard http.Client

func getQuery(body string, debug bool) (string, error) {

	var credentials = aws.Credentials{AccessKeyID: "AccessKeyID", SecretAccessKey: "SecretAccessKey"}

	reader := strings.NewReader(body)

	req, body, err := buildRequest(fmt.Sprintf("%s/v1/item/%s", apiUrl, clientUUID), reader, "GET")

	query := req.URL.Query()
	req.URL.RawQuery = query.Encode()

	signer := v4.NewSigner()

	err = signer.SignHTTP(context.Background(), credentials, req, body, service, region, time.Now())
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	if debug {
		fmt.Printf("%s\n", req.URL.String())
	}

	// An HTTP client for sending the request
	client := &http.Client{}

	resp, err := client.Do(req)
	if err != nil {
		fmt.Print(err)
		return "", err
	}

	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {

		// Check if server sent gzipped response. Decompress if yes.
		var respReader io.ReadCloser
		switch resp.Header.Get("Content-Encoding") {
		case "gzip":
			respReader, err = gzip.NewReader(resp.Body)
			defer respReader.Close()
		default:
			respReader = resp.Body
		}

		bodyString, err := ioutil.ReadAll(respReader)
		if err != nil {
			fmt.Print(err)
			return "", err
		}

		fmt.Printf("%s\n", string(bodyString))
		return string(bodyString), nil
	}
	return "", nil
}

Let’s go with builiding request to enrich our query:

// buildRequest builds an http.Request with the given body and method
func buildRequest(url string, body io.Reader, requestType string) (*http.Request, string, error) {
    const (
        TimeFormat        = "20060102T150405Z"
	    EmptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
    )

	var bodyLen int
	type lenner interface {
		Len() int
	}
	if lr, ok := body.(lenner); ok {
		bodyLen = lr.Len()
	}
	req, err := http.NewRequest(requestType, url, body)
	if err != nil {
		return nil, "", err
	}
	if bodyLen > 0 {
		req.ContentLength = int64(bodyLen)
	}

	req.Header.Add("X-Amz-Date", time.Now().UTC().Format(TimeFormat))
	req.Header.Add("date", time.Now().UTC().Format(TimeFormat))

	var payloadHash string

	if bodyLen == 0 {
		payloadHash = EmptyStringSHA256
	} else {
		h := sha256.New()
		_, _ = io.Copy(h, body)
		payloadHash = hex.EncodeToString(h.Sum(nil))
	}

	return req, payloadHash, nil
}

A couple of interesting things are happening here. We must determine if this is a POST or GET query. If it’s a GET query, we have to use EmptyStringSHA256 from the const.go file mentioned before. Also, we are going to utilise TimeFormat