node.jsaws-lambdaaws-organizationsaws-control-tower

AWS Organizations CreateAccount automation with nodeJs Lambdas not creating Account. No error messages, no error logs in CloudWatch. How to solve?


I am using AWS Nodejs Lambda to automate the Create Account process inside AWS Organizations and using Serverless framework to deploy the lambda.

Following is the Serverless.yml:

functions:
  fnPostOrganizations:
    name: fnPostOrganizations 
    handler: src/Organizations/fnPostOrganizations.fnPostOrganizations
    events:
     - http:
         path: /organizations/create_account
         method: POST
         request:
            parameters:
              querystrings:
                name: true
                token: false
                orgUnit: true
    memorySize: 256
    timeout: 900
    logRetentionInDays: 1
    iamRoleStatementsName: fnPostOrganizations-${self:provider.stage}
    iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - 'organizations:*'
      Resource: '*'

The querystrings parameters are not important for now. One possible issue here is the iamRoleStatements that allows the Lambda to create an Account in the Organization. But if that was the case I should get an error log saying not authorized or something like that. That is not happening.

And following is the actual code that should create the Organizations Account using NodeJs-16x and SDK V3:

'use strict'

const { OrganizationsClient, CreateAccountCommand } = require("@aws-sdk/client-organizations")
const client = new OrganizationsClient({ region: "us-east-1" });
console.log('🚀 client', client)


const postOrganizationsCreateAccount = async () => {
  try {
    console.log('🚀 START postOrganizationsCreateAccount')

    const params = {
      AccountName: 'testIg',
      Email: `awsTestIg@test.com`,
      IamUserAccessToBilling: 'DENY'
    }
    console.log('🚀 params', params)

    const command = new CreateAccountCommand(params)
    console.log('🚀 command', command)

    const createAccountResponse = await client.send(command)
    console.log('🚀 createAccountResponse', createAccountResponse)
    
    return createAccountResponse
  } catch (error) {
    console.log('🚀 postOrganizationsCreateAccount - error.stack:', error.stack)
    return error.stack
  }
}

I am following the Organizations Client - AWS SDK for JavaScript v3 documentation in order to create de account.

Following is the output in the Cloudwacth logs:

2022-10-16T17:14:50.989Z    undefined   INFO    🚀 client OrganizationsClient {
  middlewareStack: {
    add: [Function: add],
    addRelativeTo: [Function: addRelativeTo],
    clone: [Function: clone],
    use: [Function: use],
    remove: [Function: remove],
    removeByTag: [Function: removeByTag],
    concat: [Function: concat],
    applyToStack: [Function: cloneTo],
    identify: [Function: identify],
    resolve: [Function: resolve]
  },
  config: {
    apiVersion: '2016-11-28',
    disableHostPrefix: false,
    logger: {},
    regionInfoProvider: [AsyncFunction: defaultRegionInfoProvider],
    serviceId: 'Organizations',
    urlParser: [Function: parseUrl],
    region: [AsyncFunction: region],
    runtime: 'node',
    defaultsMode: [AsyncFunction (anonymous)],
    base64Decoder: [Function: fromBase64],
    base64Encoder: [Function: toBase64],
    bodyLengthChecker: [Function: calculateBodyLength],
    credentialDefaultProvider: [Function (anonymous)],
    defaultUserAgentProvider: [AsyncFunction (anonymous)],
    maxAttempts: [AsyncFunction (anonymous)],
    requestHandler: NodeHttpHandler { metadata: [Object], configProvider: [Promise] },
    retryMode: [AsyncFunction (anonymous)],
    sha256: [Function: bound Hash],
    streamCollector: [Function: streamCollector],
    useDualstackEndpoint: [AsyncFunction (anonymous)],
    useFipsEndpoint: [AsyncFunction: useFipsEndpoint],
    utf8Decoder: [Function: fromUtf8],
    utf8Encoder: [Function: toUtf8],
    tls: true,
    endpoint: [Function (anonymous)],
    isCustomEndpoint: false,
    retryStrategy: [AsyncFunction: retryStrategy],
    systemClockOffset: 0,
    signingEscapePath: true,
    credentials: [AsyncFunction (anonymous)],
    signer: [Function: signer],
    customUserAgent: undefined
  }
}

2022-10-16T17:14:50.995Z    91b515e5-aa3c-4eb1-a6ba-7d12fd0beef5    INFO    🚀 START postOrganizationsCreateAccount

2022-10-16T17:14:50.995Z    91b515e5-aa3c-4eb1-a6ba-7d12fd0beef5    INFO    🚀 params {
  AccountName: 'testIg',
  Email: 'awsTestIg@test.com',
  IamUserAccessToBilling: 'DENY'
}
2022-10-16T17:14:50.996Z    91b515e5-aa3c-4eb1-a6ba-7d12fd0beef5    INFO    🚀 command CreateAccountCommand {
  middlewareStack: {
    add: [Function: add],
    addRelativeTo: [Function: addRelativeTo],
    clone: [Function: clone],
    use: [Function: use],
    remove: [Function: remove],
    removeByTag: [Function: removeByTag],
    concat: [Function: concat],
    applyToStack: [Function: cloneTo],
    identify: [Function: identify],
    resolve: [Function: resolve]
  },
  input: {
    AccountName: 'testIg',
    Email: 'awsTestIg@test.com',
    IamUserAccessToBilling: 'DENY'
  }
}
END RequestId: 91b515e5-aa3c-4eb1-a6ba-7d12fd0beef5
REPORT RequestId: 91b515e5-aa3c-4eb1-a6ba-7d12fd0beef5  Duration: 145.74 ms Billed Duration: 146 ms Memory Size: 256 MB Max Memory Used: 82 MB  Init Duration: 414.12 ms    

It seems that everything is going well until the moment when it sends the command await client.send(command). After that point I do not have any log output. No error as well.

If I use AWS CLI to perform the same process I get the following:

command: aws organizations create-account --email testIgn@example.com --account-name "testIgName" --iam-user-access-to-billing "DENY"

output:

  {
      "CreateAccountStatus": {
          "Id": "car-b4be21e04bfwert6wdgf",
          "AccountName": "testIgName",
          "State": "IN_PROGRESS",
          "RequestedTimestamp": "2022-10-14T15:59:26.737000-04:00"
      }
  }

And the account is created in the Organizations.

At the CreateAccountCommand documentation it says:

Because CreateAccount operates asynchronously, it can return a successful completion message even though account initialization might still be in progress. You might need to wait a few minutes before you can successfully access the account...

But even if it is an asynchronous process I should get a CreateAccountResponse by this documentation.

At this point I don't know what is happening or how to solve this issue. Any idea?


Solution

  • Export the Lambda handler function as async.

    // ...your code above
    exports.handler = async function(event) {
      try {
        const createAccountResponse = await postOrganizationsCreateAccount(event);
        return {
          statusCode: 200,
          body: 'Account created'
        };
      } catch (error) {
        return {
          statusCode: 500,
          body: error.message
        };
      }
    }