One Cloud Please

Automating AWS Account Deletion

26 June 2019

This week Control Tower was released publicly, a managed service that replicates and iterates on the AWS Landing Zone solution previously released. It has an in-built capability of an “Account Vending Machine” which is really useful for quickly provisioning accounts to a secured state within an organization, though due to a lack of ability to programmatically delete accounts its effectiveness for the creation of ephemeral / sandbox accounts is somewhat limited.

AWS teased the idea of ephemeral accounts with an amazing talk at re:Invent 2018 and pairing blog post, though I’m yet to see any discussion on the full lifecycle of these ephemeral accounts.

Deleting an AWS account

An AWS account created with AWS Organizations has a surprisingly complex sequence of steps to actually be deleted, which is as follows:

  1. Find the accounts root user e-mail address
  2. Sign out or open an incognito tab and request a password reset
  3. Open the e-mail sent and click the provided link
  4. Set a new password and log in with the new password
  5. Set the account contact information (if not inherited from Organizations)
  6. Accept the AWS Customer Agreement (if not inherited from Organizations)
  7. Provide a valid payment method
  8. Verify yourself by phone
  9. Remove the account from Organizations within the master account
  10. Delete the account from the root user of the account

Most of the above steps are (likely deliberately) not available via an API action. This makes automating the process of removing ephemeral sandbox accounts challenging, but I wanted to see if it was possible at all.

Challenge Accepted

I wanted to create a solution, deployable via CloudFormation that makes as much use of AWS services as possible to assist with automating the above. Here’s how the solution accomplishes each of the above requirements:

Find the accounts root user e-mail address

First step is an easy one. The ListAccounts API call gives us the root account e-mail so we can call this programmatically.

Sign out or open an incognito tab and request a password reset

This step is first where I have had to break out Puppeteer to automate the tasks. Puppeteer is an browser automation tool which can be programmatically run, such as in my case using a Lambda function. The browser is mocked and will visit the AWS forgot password page and request a reset.

Unfortunately this step will always require a CAPTCHA solve to combat against exactly what I’m trying to do. There are services available that will solve these for you in a reasonable amount of time. My favourite is 2captcha which will solve a simple CAPTCHA image at a rate of $0.50-$1.00 per thousand solves which is very reasonable.

So I have Puppeteer download the CAPTCHA image presented and upload that to the 2captcha servers for a result. I’ll typically receive a response in 10-20 seconds, which I then use to continue with the password reset.

An e-mail is sent to the account owner with a reset link which I need to visit in order to set the root account password. When setting up the accounts, I use a dedicated domain / subdomain for all root account which I set up in Route 53. I set the MX (mail) record for this domain / subdomain to point to the SES inbound address and use an SES Receipt Rule to process the mail, which is stored in S3 and triggers a Lambda to process the contents of this e-mail.

The Lambda will parse the e-mail body and look for links that looks like the reset link format. A lot of the time these e-mails are just some of the promotional / “helpful” e-mails they sent to the account holder, so many of these e-mails are simply skipped over.

Set a new password and log in with the new password

Once a correctly formatted link is found, we again use Puppeteer to automate a browser to set a root user password and then login. In the automation, I use AWS Secrets Manager to generate a random root user password for this process.

Set the account contact information (if not inherited from Organizations)

I’ve never had to do this as I believe it’s inherited from the organization. Check this is set up in your master before attempting the automation.

Accept the AWS Customer Agreement (if not inherited from Organizations)

I’ve also never had to perform this. It seems to be automatically performed when creating the account in AWS Organizations.

Provide a valid payment method

It is unfortunate that we can’t tokenize a payment method and use it for child accounts. We again have to use Puppeteer to enter in the payment details which I retrieve from an AWS Secrets Manager secret I suggest you make before deploying the automation. I’d recommend using a reloadable debit card for this to reduce the risk.

Verify yourself by phone

It’s not well documented, but one of the most common missed steps when making an account independant is the phone verification. To do this, you must visit this special link which throws you half-way into the standard onboarding flow you would go through with a standalone account. The step requests a phone number capable of receiving calls which only when entered will give you a 4 digit code for you to enter via your phone keypad which verifies you.

Amazon Connect is targetted as a virtual call center solution, but I wanted to determine whether this could be used to automate this phone verification. I had previously created the CloudFormation Provider for Amazon Connect solution which again uses Puppeteer to automate the creation and management of Connect instances as there are no APIs available for this yet.

One thing that Amazon Connect cannot do is execute DTMF tones (that’s the beeping tones you hear when using your keypad) whilst in an IVR flow. What I discovered however is that you could play arbitrary sound prompts by having it invoke a Lambda and determining which predefined prompts to play. The DTMF tones are indeed simply sounds that are interpreted over the line, so this solution is actually viable. The DTMF tone .wav files are publicly available and can be used as prompts in Amazon Connect if uploaded prior.

I use Puppeteer to first create the Amazon Connect instance, register a contact flow, register a phone number and use that phone number in the verification step. The contact flow has a 10 second wait at the start on order to both give the Amazon verification call time to say some generic instructions and also to have Puppeteer read the digits presented on screen and update the Lambda environment to ensure the correct prompt IDs that correlate to the digits are given when the wait time is complete.

As an aside, you don’t have visibility into the call as audio before an agent picks up is not recorded by the Amazon Connect call recording feature. Luckily, when testing this it worked the very first time.

Remove the account from Organizations within the master account

At this point, the hard parts are done and all the prerequisites for removing the account should be met (unless the account was created in the last week). The call to remove the account from the organization is one that has an API call, RemoveAccountFromOrganization, so this is easily automated.

Delete the account from the root user of the account

One last time, we need to use the session that is logged in via the root user account and delete the account with Puppeteer. This requires 4 checkboxes and 2 confirmations. Once this is done, the process is complete and the account is fully removed.

The caveats

The caveats with this type of automation are important to note:

  • This almost certainly violates the AWS Terms of Service - AWS could easily justify terminating your account or linked accounts within the organization as a result
  • Accounts that are created within 7 days cannot leave an organization - you will receive an error if you try
  • There is a limit on the amount of accounts permitted to be created per day - I’ve never reached this and tried ~10 account creations in a day
  • There is a limit on the amount of accounts permitted to be created in total - this is also surprisingly low (single digits) by default however I didn’t get any pushback from support when requesting a limit increase for this to 15
  • The payment method being stored is scary - but unfortunately I don’t have a better solution for this (let me know if you have a suggestion)
  • Resource usage still applies - any resource usage between the last billing cycle and the time we terminate the account will go on the billing method that is stored

Trying it yourself

AN IMPORTANT MESSAGE: I don’t actually recommend you do this. This is an experiment to stretch the capabilities of what automation can do and so the solution is probably very dangerous. DO NOT use this with any account linked to a production workload.

I’m still working on releasing an easy-to-use template to consume all of the automation above but when I release it, it will be available here. If you are interested in testing or consuming these templates, please reach out.


It’s unfortunate that AWS hasn’t released functionality to programmatically remove the accounts it allows us to create, so it is my hope that we see some change in this area soon. With some creativity, a lot of things can be automated however automating account deletions in its current state is right at the edge of what is practical.