AWS Lambda HTTP request to Neptune with openCypher query fails

219 Views Asked by At

Running the below Lambda for most openCypher queries fails.

This is a NodeJS 18.x Lambda, attempting to send an HTTP request to a Neptune Writer endpoint.

The Lambda works fine when the openCypher query does not contain any strings. This simple query is working fine:

const ocQuery = "MATCH (n) RETURN n LIMIT 1";

But this is failing (Server error 500):

const ocQuery = "MATCH (n {name: 'Israel'}) RETURN n";

And this is failing (Error 403 Access Denied):

const ocQuery = "MATCH (n) WHERE n.name = 'Israel' RETURN n";

I tried escaping the quote char and others, tried encoding these special chars - nothing solved it.

Lambda code:

import axios from 'axios';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';

const {
  AWS_ACCESS_KEY_ID,
  AWS_SECRET_ACCESS_KEY,
  AWS_SESSION_TOKEN
} = process.env;

const ocQuery = "MATCH (n) WHERE n.name = 'Israel' RETURN n";
const API_URL = "https://db-simplify-mvp-dev-instance-1.cwezylrm9ic8.us-east-1.neptune.amazonaws.com:8182/openCypher?query=" + ocQuery;

const apiUrl = new URL(API_URL);

const sigv4 = new SignatureV4({
  service: 'neptune-db',
  region: 'us-east-1',
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    sessionToken: AWS_SESSION_TOKEN,
  },
  sha256: Sha256,
});


export const handler = async () => {
  
  const signed = await sigv4.sign({
    method: 'GET',
    hostname: apiUrl.host,
    path: apiUrl.pathname,
    protocol: apiUrl.protocol,
    query: {
      query: ocQuery
      },
    headers: {
      'Content-Type': 'application/json',
      host: apiUrl.hostname, 
    }
  });

  try {
    const result = await axios({
      ...signed,
      url: API_URL,
    });

    console.log('Successfully received result: ', result.data);
    return {
      statusCode: 200,
      body: result.data
    }
  } catch (error) {
    console.log('An error occurred', error);

    throw error;
  }
};

The Lambda IAM execution role has these policies:

AmazonEC2FullAccess
NeptuneFullAccess
AWSLambdaBasicExecutionRole
AmazonSSMReadOnlyAccess
AWSLambdaVPCAccessExecutionRole

VPC Configuration:

enter image description here

The Neptune Security Group has inbound rules to allow the Lambda Security Group on port 8182:

enter image description here

Tried using POST method, same error. What am I missing, please?

EDIT: Updated Lambda code with the solution offered by Taylor that worked great until I attempted to add query parameters. Originally I received this error, which is mostly not showing anymore:

The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.
1

There are 1 best solutions below

6
On BEST ANSWER

You were really close. I think the one big thing that you were missing was to include the query parameters as part of the SigV4 signing process. You also do not need to URL-encode the openCypher query. You can leave the spaces and such as shown below. You'll have to do that, or else the Signature will not get created properly.

I also had to change the way that the results were being returned from the Axios request. See updated code here:

import axios from 'axios';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';

const {
  AWS_ACCESS_KEY_ID,
  AWS_SECRET_ACCESS_KEY,
  AWS_SESSION_TOKEN
} = process.env;

const ocQuery = "MATCH (n) RETURN n LIMIT 1";
const API_URL = "https://neptunedbcluster-abcdefghijkl.cluster-abcdefghijkl.us-west-2.neptune.amazonaws.com:8182/openCypher?query=" + ocQuery;

const apiUrl = new URL(API_URL);

const sigv4 = new SignatureV4({
  service: 'neptune-db',
  region: 'us-west-2',
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    sessionToken: AWS_SESSION_TOKEN,
  },
  sha256: Sha256,
});


export const handler = async () => {
  
  const signed = await sigv4.sign({
    method: 'GET',
    hostname: apiUrl.host,
    path: apiUrl.pathname,
    protocol: apiUrl.protocol,
    query: {
      query: ocQuery
      },
    headers: {
      'Content-Type': 'application/json',
      host: apiUrl.hostname, 
    }
  });

  try {
    const result = await axios({
      ...signed,
      url: API_URL,
    });

    console.log('Successfully received result: ', result.data);
    return {
      statusCode: 200,
      body: result.data
    }
  } catch (error) {
    console.log('An error occurred', error);

    throw error;
  }
};