Uploaded image for project: 'Node.js Driver'
  1. Node.js Driver
  2. NODE-6393

Authenticate lambda with IAM credentials obtained via STS

    • Type: Icon: New Feature New Feature
    • Resolution: Unresolved
    • Priority: Icon: Unknown Unknown
    • None
    • Affects Version/s: None
    • Component/s: None
    • 1
    • Hide

      1. What would you like to communicate to the user about this feature?
      2. Would you like the user to see examples of the syntax and/or executable code and its output?
      3. Which versions of the driver/connector does this apply to?

      Show
      1. What would you like to communicate to the user about this feature? 2. Would you like the user to see examples of the syntax and/or executable code and its output? 3. Which versions of the driver/connector does this apply to?

      Lambda role chaining IAM authentification.

      Currently it is not possible to use role chaining to authenticate via IAM without connection / auth issues. But this is a common ScreenOrientation.

      Our use case:
      Lamnbda has execution role that allows function to assume role MONDO_DB_READ. This roe is registered as an Atlas user so the lambda fn can authenticate with our cluster. We need role chaining (instead of using the execution role directly for auth) for security compliance reasons.

      Issue:
      Roles assumed via STS (role chaining) are only valid for an hour. If we provide the credentials obtained via STS directly to a MongoClient, the initial connection works, but after an hour (we have busy lambdas with execution contexts lasting longer than that), the connection to our Mongo cluster suddenly fails due to expired credentials. The MongoDB node driver makes it impossible to use other AWS credential providers than fromNodeProviderChain.

      Solution:
      Use the AWS sdk to get a credential provider. The SDK handles credential rotation. MongoDB node driver does support this (and uses it internally). World could be a happy place.

      But:
      The Node driver uses a specific credential provider from the AWS sdk called fromNodeProviderChain (https://github.com/mongodb/node-mongodb-native/blob/643a87553b5314abb44bcdc342a8bb8cb51d2052/src/cmap/auth/aws_temporary_credentials.ts#L99C49-L99C70)

      This does not work for our case, as we would need the fromTemporaryCredentials provider.

      So we monkey-patched the mongodb lib like this:

      import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
      
      async function patchMongoAuth(): Promise<void> {
        const { AWSSDKCredentialProvider } = await import(
          // @ts-ignore
          'mongodb/lib/cmap/auth/aws_temporary_credentials.js'
        );
      
        AWSSDKCredentialProvider.prototype.getCredentials =
          async function (): Promise<{
            AccessKeyId: string;
            Expiration?: Date;
            SecretAccessKey: string;
            Token?: string;
          }> {
            const provider = fromTemporaryCredentials({
              clientConfig: { region: 'eu-central-1' },
              params: { RoleArn: 'our-role-arn', DurationSeconds: 3600 },
            });
            const { accessKeyId, expiration, secretAccessKey, sessionToken } =
              await provider();
            return {
              AccessKeyId: accessKeyId,
              Expiration: expiration,
              SecretAccessKey: secretAccessKey,
              Token: sessionToken,
            };
          };
      }
      
      

      This works, but not in a lambda environment, since the Node driver set username and password by default to the AWS credentials stored in the environemnt variables. This happens in the `MongoCredentials` class constructor: MongoCredentials

      But when the username is set then AWSSDKCredentialProvider.getCredentials is never called. But in a lambda environemnt the `AWS_ACCESS_KEY_ID` is always set.

      This means, we now need to monkey patch the auth method as well and modify the auth context:

      async function patchMongoAuth(): Promise<void> {
        const { AWSSDKCredentialProvider } = await import(
          // @ts-ignore
          'mongodb/lib/cmap/auth/aws_temporary_credentials.js'
        );
        const { MongoDBAWS } = await import(
          // @ts-ignore
          'mongodb/lib/cmap/auth/mongodb_aws.js'
        );
      
        const originalAuth = MongoDBAWS.prototype.auth;
        MongoDBAWS.prototype.auth = async function (authContext: any) {
          authContext.credentials = {
            source: '$external',
            mechanism: 'MONGODB-AWS',
            mechanismProperties: {},
          };
          return originalAuth.call(this, authContext);
        };
      
        AWSSDKCredentialProvider.prototype.getCredentials =
          async function (): Promise<{
            AccessKeyId: string;
            Expiration?: Date;
            SecretAccessKey: string;
            Token?: string;
          }> {
            const provider = fromTemporaryCredentials({
              clientConfig: { region: 'eu-central-1' },
              params: { RoleArn: 'our-role-arn', DurationSeconds: 3600 },
            });
      
            const { accessKeyId, expiration, secretAccessKey, sessionToken } =
              await provider();
            return {
              AccessKeyId: accessKeyId,
              Expiration: expiration,
              SecretAccessKey: secretAccessKey,
              Token: sessionToken,
            };
          };
      }
      

      Please
      Could you make the AWS credential provider configurable? Or provide a different solution for this use case? I think it is rather common. If you have hundreds of lambdas, it is not feasible nor advisable from a security perspective to register each execution role in Atlas.

            Assignee:
            Unassigned Unassigned
            Reporter:
            florian@easypliant.de Florian Norbert Bischoff
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: