amazon-web-serviceshttp-status-code-403opensearchopensearch-serverless

AWS Opensearch serverless - 403 Forbidden for API access attempt


I have setup an AWS opensearch serverless collection and have one index.

In my opensearch serverless I have the following data access policy:

[
  {
    "Rules": [
      {
        "Resource": [
          "collection/*"
        ],
        "Permission": [
          "aoss:CreateCollectionItems",
          "aoss:DeleteCollectionItems",
          "aoss:UpdateCollectionItems",
          "aoss:DescribeCollectionItems"
        ],
        "ResourceType": "collection"
      },
      {
        "Resource": [
          "index/*/*"
        ],
        "Permission": [
          "aoss:*"
        ],
        "ResourceType": "index"
      }
    ],
    "Principal": [
      "arn:aws:iam::MY_ACCOUNT:role/my_lambda_role",
    ]
  }
]

(For testing purposes I have given my_lambda_role full access to all resources including aoss:APIAccessAll etc)

When I run the following lambda function (using my_lambda_role as the execution role):

const AWS = require('aws-sdk');

exports.handler = async (event) => {
  const endpoint = new AWS.Endpoint('1234abcd.us-east-1.aoss.amazonaws.com');
  const region = 'us-east-1'; 
  const index = 'cats_and_dogs'; 

  // Create the HTTP request
  const request = new AWS.HttpRequest(endpoint, region);
  request.method = 'POST';
  request.path = `/${index}/_search`;
  request.headers['host'] = endpoint.host;
  request.headers['Content-Type'] = 'application/json';
  
  request.body = JSON.stringify({
    query: {
      match: {
        description: "farm"
      }
    }
  });

  // Sign the request using AWS Signature Version 4
  const signer = new AWS.Signers.V4(request, 'aoss');
  signer.addAuthorization(AWS.config.credentials, new Date());

  // Send the request using AWS HttpClient
  return new Promise((resolve, reject) => {
    const client = new AWS.HttpClient();
    client.handleRequest(
      request,
      null,
      (response) => {
        let responseBody = '';
        response.on('data', (chunk) => {
          responseBody += chunk;
        });
        response.on('end', () => {
          resolve({
            statusCode: response.statusCode,
            body: JSON.parse(responseBody),
          });
        });
      },
      (error) => {
        console.error('Request error:', error);
        reject({
          statusCode: 500,
          body: JSON.stringify({ error: 'Failed to execute search' }),
        });
      }
    );
  });
};

I get 403 Forbidden:

{
  "statusCode": 403,
  "body": {
    "status": 403,
    "error": {
      "reason": "403 Forbidden",
      "type": "Forbidden"
    }
  }
}

Solution

  • The problem was that AWS.Signers.V4() was not adding the 'X-Amz-Content-Sha256' header which is required in opensearch serverless but not opensearch self managed it seems (I never head it when using standard opensearch self managed):

    Solution:

    // Sign the request using AWS Signature Version 4
    const signer = new AWS.Signers.V4(request, 'aoss');
    signer.addAuthorization(AWS.config.credentials, new Date());
    request.headers['X-Amz-Content-Sha256'] = signer.hexEncodedHash(request.body);