ldapjs / node-ldapjs

LDAP Client and Server API for node.js
http://ldapjs.org
MIT License
1.6k stars 423 forks source link

Search not working in AWS Lambda #729

Closed smasilamani-cfins closed 3 years ago

smasilamani-cfins commented 3 years ago

Hello

I am building a small service where we need to get the user details from LDAP. I was able to search the users successfully when I run the code in my desktop however once the code is deployed in AWS lambda, search is not working, from the logs I can see that the ldap connection is good and there are no errors. its just that search is not returning the user info. Please advise.

import { Client, SearchOptions, createClient } from 'ldapjs';

import { Injectable } from '@nestjs/common';
import { Config} from '@shared/config/app.config';
import { LogService} from '@shared/services/log/log.service';
import { SecretManagerService} from '@shared/services/secret/secret_manager.service';

@Injectable()
export class DSLdap {
    private client: Client = null;

    constructor(private readonly logService: LogService, private readonly config: Config, private readonly secretService: SecretManagerService) {}

    async config(): Promise<void> {
        let username: string;
        let password: string;

        const secretString: string = await this.secretService.getSecretValue(this.dsConfig.ldap.secretKeyName);
        if (secretString) {
            this.logService.info('LDAP secret retrieved successfully.');
            ({ username, password } = JSON.parse(secretString));
        }
        this.logService.info('Connecting to LDAP server...');
        this.client = createClient({
            url: this.config.ldap.url,
            bindCredentials: '',
            tlsOptions: {
                rejectUnauthorized: false
            },
            log: this.logService
        });
        this.logService.info('Connected to LDAP server...');

        await new Promise((resolve, reject) => {
            this.logService.info('Binding to LDAP server...');
            this.client.bind(username, password, (err) => {
                if (err) {
                    reject(err);
                }
                this.logService.info('Bound to LDAP server...');
                return resolve(true);
            });

            this.client.on('error', (error) => {
                this.logService.error(error);
                return reject(error);
            });
        });
    }

    async searchByEmail(emailAddress: string): Promise<any> {
        const opts: SearchOptions = {
            filter: `(userPrincipalName=${emailAddress})`,
            scope: 'sub'
        };
        const entries: any = [];
        const referrals: any = [];
        return new Promise((resolve, reject) => {
            this.client.on('error', (error) => {
                this.logService.error(error);
                return reject(error);
            });

            this.client.search(this.config.ldap.baseDn, opts, (err, resp) => {
                if (err) {
                    reject(err);
                }
                resp.on('searchEntry', (entry) => {
                    this.logService.info('Entry ' + entry);
                    entries.push(entry.object);
                });
                resp.on('searchReference', (referral) => {
                    this.logService.info('Referral ' + referral);
                    referrals.push(referral.uris);
                });

                resp.on('error', (error) => {
                    this.logService.error(error);
                    return reject(error);
                });

                resp.on('end', (result) => {
                    this.logService.info('Result ' + result);
                    return resolve({
                        entries: entries,
                        referrals: referrals
                    });
                });
            });
        });
    }
}
jsumners commented 3 years ago

Please provide a minimal reproduction in JavaScript.

smasilamani-cfins commented 3 years ago

Please provide a minimal reproduction in JavaScript.

Thanks for getting back to me. We are using Typescript for our server side and the code given above is the one we use.

UziTech commented 3 years ago

this.client is an event emitter so you should subscribe to this.client.on('error' (error) => {...}) to see if there are any errors thrown there.

UziTech commented 3 years ago

sorry wrong button...

smasilamani-cfins commented 3 years ago

sorry wrong button...

@UziTech

Thank you for your input. I did not realize that client itself is a subscriber. I added the error event and also updated the code above but still I do not see anything in the log. The same code is working fine in my desktop, however my desktop is in the same network/domain as of our ldap server and I am not sure if it has anything to do with it. I can see that the service was able to connect and perform the search however no info is returned. This is the output I got from end event.

Result {"messageID":3,"protocolOp":"LDAPResult","status":0,"matchedDN":"","errorMessage":"","referrals":[],"controls":[]}

Logs from Lambda startup.

image

Endpoint log:

image

jsumners commented 3 years ago

Thanks for getting back to me. We are using Typescript for our server side and the code given above is the one we use.

This project does not provide support for transpiled code. I also am not willing to try and interpret TypeScript. If you are unable or unwilling to provide a minimal reproduction of the error in JavaScript then I cannot help you.

UziTech commented 3 years ago

It seems like everything is working if end is called. The only thing I can think is the server isn't returning any results for the query for some reason. Unfortunately we are not able to help with that.

UziTech commented 3 years ago

I did notice one bug in your code:

  this.client.search(this.config.ldap.baseDn, opts, (err, resp) => {
    if (err) {
-     reject(err);
+.    return reject(err);
    }
smasilamani-cfins commented 3 years ago

@UziTech @jsumners

Thanks again. I will try to convert this code to JavaScript later and can share it with you. I will also check with our LDAP admin if they see anything on their side.

DanielSamsonraj commented 3 years ago

@saachinsiva Were you able to solve this issue?

smasilamani-cfins commented 3 years ago

@DanielSamsonraj

Not yet but I believe the LDAP server is throwing certificate error that has been ignored silently by this library. I may need to add the certificate to the truststore to see if it works. I have been working on other high priority item not but will keep you posted if I make any progress.

smasilamani-cfins commented 3 years ago

@DanielSamsonraj

I tried adding the certificate to the nodejs env and it works perfectly fine in local but still no luck in lambda. No error nothing...just get empty response.

smasilamani-cfins commented 3 years ago

Hello Everyone,

I was able to make it work. The problem is with webpack. When we bundle the application, webpack config uses managle and also rename the functions and classes name to smaller character in order to save space which affects the event emitter. In my case the function name SearchEntry is replaced with character 'y' due to mangle and this causes issue when it reaches this line of code " msg.constructor.name" in manageCallback shown below where it expects the constructor name as SearchEntry which will then be changed to searchEntry and send that as an event. However in my case the constructor name is coming as 'y' and the event is emitted as 'y' which is why it is not getting any results. In order to keep the function name and class name I used below config for my webconfig.

From client.js

 function messageCallback (msg) {
    if (timer) { clearTimeout(timer) }

    log.trace({ msg: msg ? msg.json : null }, 'response received')

    if (expect === 'abandon') { return sendResult('end', null) }

    if (msg instanceof SearchEntry || msg instanceof SearchReference) {
      let event = msg.constructor.name
      event = event[0].toLowerCase() + event.slice(1)
      return sendResult(event, msg)
    } else {
      tracker.remove(message.messageID)
      // Potentially mark client as idle
      self._updateIdle()

      if (msg instanceof LDAPResult) {
        if (expect.indexOf(msg.status) === -1) {
          return sendResult('error', errors.getError(msg))
        }
        return sendResult('end', msg)
      } else if (msg instanceof Error) {
        return sendResult('error', msg)
      } else {
        return sendResult('error', new errors.ProtocolError(msg.type))
      }
    }
  }

Webpack config:

mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
    entry: entries,
    devtool: 'source-map',
    optimization: {
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    mangle: true,
                    keep_classnames: true,
                    keep_fnames: true, // important to keep the event name from ldapjs
                    sourceMap: true
                },
                parallel: true
            })
        ]
    },
jsumners commented 3 years ago

Closing this as it sounds like you have found your issue and solution.

Reminder: we do not support transpiled versions of the library.

jsumners commented 1 year ago

⚠️ This issue has been locked due to age. If you have encountered a recent problem that seems to be covered by this issue, please open a new issue.

Please include a minimal reproducible example when opening a new issue.