0

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.
Mor Sagmon
  • 905
  • 1
  • 16
  • 35

1 Answers1

2

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;
  }
};
Taylor Riggan
  • 1,963
  • 6
  • 12
  • many thanks for your great push yesterday @Taylor Riggan. I am now trying to add query parameters and fail to get it to pass the signature again. I edited the original question up here. Your guidance is much appreciated, please! – Mor Sagmon Feb 22 '23 at 10:10
  • 1
    Unfortunately, you cannot do the parameterization that way. You'll have to build the entire openCypher query first and then pass that as the query parameter for building the SigV4 Signature. – Taylor Riggan Feb 22 '23 at 12:47
  • Oh, so I will populate the params values into the query string on my app, passing the complete single string to the Lambda for processing. Thank you for the guidance! – Mor Sagmon Feb 22 '23 at 14:27
  • It doesn't work for me yet. tried passing this as ocQuery: 'MATCH (n %7Bname:%22Israel%22%7D) RETURN n LIMIT 1' - got the same signature calculation error. Any change to the original simple query with a string inside results in Access Denied or signature calculation error. – Mor Sagmon Feb 22 '23 at 15:10
  • I updated the original question accordingly. Thanks! – Mor Sagmon Feb 22 '23 at 15:47
  • OK. Got it to work but I don't like it. ocQuery has the curly brackets encoded but the query param in the signed request must have them UNencoded. ocQuery = "MATCH (n %7B name: 'Israel'%7D) RETURN n"; and query: "MATCH (n { name: 'Israel'}) RETURN n" - this should NOT be like that – Mor Sagmon Feb 22 '23 at 16:25