The Ultimate Troubleshooting Tool : AWS Lambda + CloudWatch

As part of my role, I work with developers to help them improve SCIM APIs for SailPoint IdentityNow and IdentityIQ Sources and Applications. I am also involved with the creation and testing of SaaS Connectors. I also get involved with Web Services Connectors for products with a REST API. There is one tool that has been a life saver for me over the last few years, and I would like to share this tool here, so others can also save themselves some time and efforts when working with IdentityNow and IdentityIQ.

Very often, the logs don’t tell us the full story. Sometimes, I wish I could see all the Requests made by IdentityNow and IdentityIQ, and all the Responses coming from the API. Sometimes I would like to simulate an API change before I ask Product Dev to implement it, to confirm this is the change that is needed to make things work. I will show you an AWS Lambda function I put together to act as a transparent proxy, and how we can leverage AWS CloudWatch to get the full story. Then I will show you how it’s made and share the code, which is in nodejs.


AWS Lambda function with Function URL.


We need to copy the Function URL as a prefix to the Host URL.


Import Entitlements with Manual Aggregation.

Now let’s take a look at AWS CloudWatch for our Lambda Function.


First Request: GET /ResourceTypes


First Response: Resource Types.


Second Request: GET /Schemas.


Third Request: GET /Roles.

Now we know exactly what are the Requests that the IdentityNow SCIM 2.0 Source sends to the Source API interface. This is very useful for example when troubleshooting an error message in IdentityNow or the VA logs.

We can also use our AWS Lambda Function with IdentityIQ.


Insert the Function URL as a prefix for IdentityIQ Application Base URL.


IdentityIQ first Request in CloudWatch.

Creating the AWS Lambda Function

The first step is to get an AWS Account. AWS provides a Free Tier option for developers.

Then we need to create a Layer for axios, a popular module for nodejs, to support outgoing API calls.
You can find a step-by-step for creating the layer for the axios module here.

You can download a preconfigured axios module here:
axios.zip (588.5 KB)

After we import the axios Layer in AWS Lambda, we need to add it to our Function by using its ARN. We also need to add a Function URL to our Function, with Auth type set to None.

Below you will find the code, which includes a lot of console.log invocations so we can use CloudWatch to get as much information as we want.

I hope you have enjoyed this blog post, and that it inspired you to add an AWS Lambda Function to your toolkit !


AWS Lambda Function: Code source.

import axios from 'axios';

export const handler = async (event) => {
  // Extract host, endpoint and query from Request Url
  console.log('######## event INCOMING =  '+JSON.stringify(event));
  const method = event.requestContext.http.method;
  console.log('event method = '+method);
  var endpoint = 'https:/'+event.rawPath;
  if(event.rawQueryString){endpoint = endpoint+'?'+event.rawQueryString;}
  var host = event.rawPath.substring(1);
  host = host.substring(0,host.indexOf('/'));
  console.log('host = '+host+'   endpoint = '+endpoint);
  // Change Header host value for gateway
  event.headers.host = host;
  // GET data from target
  if(event.headers['content-type']) {'Content-Type =  '+console.log(event.headers['content-type']);}
  let config = {};
  if(event.body) {
    console.log('body = '+JSON.stringify(event.body));
  config = {
    method: method,
    url: endpoint,
    headers: {'content-type': event.headers['content-type'],'accept-encoding': event.headers['accept-encoding'],'content-length': event.headers['content-length'],accept: event.headers.accept,authorization: event.headers.authorization,host: host,'user-agent': event.headers['user-agent'],connection: event.headers.connection},
    data: event.body
  };
  } else {
  config = {
    method: method,
    url: endpoint,
    headers: {'content-type': event.headers['content-type'],'accept-encoding': event.headers['accept-encoding'],'content-length': event.headers['content-length'],accept: event.headers.accept,authorization: event.headers.authorization,host: host,'user-agent': event.headers['user-agent'],connection: event.headers.connection},
  };
  }
    console.log('axios config = '+JSON.stringify(config));
  try{
    let res = await axios(config);
    console.log('axios res data  = '+JSON.stringify(res.data));
    console.log('axios res headers = '+JSON.stringify(res.headers));
  const response = {
      statusCode: 200,
      body: JSON.stringify(res.data),
      headers: res.headers
    };
  return response;
  } catch(error){
      console.log('error = '+JSON.stringify(error));
      if( error.response ){
        console.log('error response data = '+JSON.stringify(error.response.data)); // => the response payload 
      }
      const myError = JSON.parse(JSON.stringify(error));
      console.log('error.status = '+myError.status);
      if(myError.status === 401){
        return {
          statusCode: myError.status,
          headers: {'www-authenticate': `Bearer error="invalid_token", error_description="The token expired at '02/15/2024 00:01:01'"`}
        };
      }
      else{
        return {
          statusCode: myError.status
        };
        
      }
  }

};

1 Like