Skip to content

AWS SigV4 Signing Request for any Services

Updated: at 12:00 AM

AWS Signature Version 4 (SigV4) is the recommended method for authenticating requests to AWS services. It ensures that requests are tamper-proof and securely signed. By leveraging SigV4, you can add an extra layer of security to your requests while interacting with AWS services, such as API Gateway.

Table of Content

AWS SigV4

With sigV4, user’s secrets never appear API requests directly, and with its design meaning every request is finely scoped down and signed with a token that is valid only for a given AWS service, in a given AWS region, on a given day. All of these are checked and verified at the authenticated stage, before AWS tries to authorise a request.

Facts and Figures

Here is the code for lambda (or any other AWS Service) to signs the requests which is intended for AWS OpenSearch Serverless:-

Example using Node.js

const aws4 = require("aws4"); // npm i aws4
const emptyHash =
  "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
const AOSS_HOST = "1ad917d3d5dc.eu-west-1.aoss.amazonaws.com";

const request = {
  host: AOSS_HOST,
  method: "GET",
  path: "/my-index",
  service: "aoss", // AWS Service
  region: "eu-west-1",
  headers: {
    "Content-Type": "application/json",
    "X-Amz-Content-Sha256": emptyHash,
  },
  //body: JSON.stringify({ /* Request body */ }), // For POST, PUT etc
};
const signedRequest = aws4.sign(request);

(async () => {
  const requestOptions = {
    method: request.method,
    headers: signedRequest.headers,
  };
  console.log({ ...requestOptions, url: AOSS_HOST });

  const response = await fetch(
    `https://${AOSS_HOST}/${request.path}`,
    requestOptions
  );
  const result = await response.text();
})();

Example using Java

import com.amazonaws.DefaultRequest;
import com.amazonaws.Request;
import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.regions.Regions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

public class AwsSearch {
private static final String HOST = "https://1ad917d3d5dc.eu-west-1.aoss.amazonaws.com";
private static final String RESOURCE_PATH = "/prefix/_search";
private static final String SHA256_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";

    public static String search() {
        try {
            RequestParams requestParams = new RequestParams("aoss", HOST, RESOURCE_PATH,HttpMethodName.POST, Regions.EU_WEST_1);
            Request<?> request = signRequest(requestParams);

            HttpPost postRequest = new HttpPost(HOST + RESOURCE_PATH);
            for (Map.Entry<String, String> headerEntry : request.getHeaders().entrySet()) {
                postRequest.addHeader(headerEntry.getKey(), headerEntry.getValue());
            }

            StringEntity entity = new StringEntity("{\"from\": 0, \"size\": 1000, \"query\":{\"match_all\": {} }}");
            postRequest.setEntity(entity);

            try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
                HttpResponse response = httpClient.execute(postRequest);
                return EntityUtils.toString(response.getEntity());
            }

        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }

    private static Request<?> signRequest(RequestParams params) throws URISyntaxException {
        AwsCredentials awsCredentials = new AwsCredentials();
        BasicAWSCredentials credentials = new BasicAWSCredentials(awsCredentials.awsAccessKeyId, awsCredentials.awsSecretAccessKey);
        Request<?> request = new DefaultRequest<>(params.serviceName);
        request.setHttpMethod(params.methodName);
        request.setEndpoint(new URI(params.host));
        request.setResourcePath(params.path);
        request.addHeader("Content-Type", "application/json");
        request.addHeader("X-Amz-Security-Token", awsCredentials.awsSessionToken);
        request.addHeader("X-Amz-Content-Sha256", SHA256_HASH);

        AWS4Signer signer = new AWS4Signer();
        signer.setRegionName(params.region.getName());
        signer.setServiceName(request.getServiceName());
        signer.sign(request, credentials);
        return request;
    }

    @Data
    @AllArgsConstructor
    private static class RequestParams {
        String serviceName;
        String host;
        String path;
        HttpMethodName methodName;
        Regions region;
    }

    @Getter
    private static class AwsCredentials{
        private final String awsAccessKeyId;
        private final String awsSecretAccessKey;
        private final String awsSessionToken;

        public AwsCredentials() {
            this.awsAccessKeyId = System.getenv("AWS_ACCESS_KEY_ID");
            this.awsSecretAccessKey = System.getenv("AWS_SECRET_ACCESS_KEY");
            this.awsSessionToken = System.getenv("AWS_SESSION_TOKEN");
        }
    }
}

// Usage
search();