Tag Archives: AWS

Getting Started with Cartography for AWS

I have recently been working with Cartography. This tool is great for taking stock of your infrastructural and security assets, visualising them, and running security audits. However, getting it to work the first time is more painful than it needs to be. Through this article, I hope to make it less painful for other people checking out Cartography for the first time.

What is Cartography?

Cartography is a tool that can explore cloud and Software as a Service (SaaS) providers (such as AWS, Azure, GCP, GitHub, Okta and others), gather metadata about them, and store it in a Neo4j graph database. Once in Neo4j, the data can be queried using the Cypher language and the results can be visualised. This is extremely useful to understand the relationship between different infrastructural and security assets, which can sometimes reveal security flaws that need to be addressed.

Cartography is written in Python and maintained by Lyft. Sacha Faust’s “Automating Security Visibility and Democratization” 30-minute talk at BSidesSF 2019 serves as a great intro to Cartography, and also illustrates several of the early data relationships it collected.

Good to Know

Before we dive into setting up Cartography and its dependencies, I want to point out some issues I ran into, in order to minimise frustration.

[Update 8th July 2023: all issues in this section have by now been fixed, so you can skip this section. You can use a newer version of Neo4j now, although the rest of the article still uses Neo4j 3.5 for historical reasons.]

The biggest of these is that Cartography still requires the outdated Neo4j 3.5, which was planned to reach its end-of-life on 28th November 2021. Although a pull request for migration to Neo4j 4.4 was contributed on 30th January 2021, the Lyft team completely missed this deadline. Fortunately, support for Neo4j 3.5 was extended to 27th May 2022. Although the maintainers are planning to migrate to migrate to a newer Neo4j version by then, I’m not holding my breath.

This worries me for a number of reasons:

  1. If Neo4j 3.5 reaches end of life before Cartography have migrated to a more recent version, it means people using Cartography would need to run an unsupported version of Neo4j. This could be a security risk, which is ironic given that Cartography is a tool used for security.
  2. It gives the feeling that Cartography is not very well-maintained, if issues as important as this take well over a year to resolve.
  3. It makes it virtually impossible to run Cartography on a Mac with one of the newer Apple M1 CPUs. That’s because Neo4j 3.5 won’t run on an arm64 processor (e.g. Neo4j Docker images for this architecture started to appear only since 4.4), but also because a Python cryptography dependency needs to be upgraded.

So if you feel you need to depend on Cartography, it might make sense to fork it and maintain it yourself. Upgrading it to support Neo4j 4.4 is tedious but not extremely complicated, and mostly is a matter of updating Cypher queries to use the new parameter syntax as explained in the aforementioned pull request.

Another problem I ran into (and reported) is that Cartography gets much more EBS snapshot data than necessary. This bloats the Neo4j database with orders of magnitude of unnecessary data, and makes the already slow process of data collection take several minutes longer than it needs to.

Setting Up Neo4j

For now, we’ll have to stick with Neo4j 3.5. You can follow the Cartography Installation documentation to set up a local Neo4j instance, but it’s actually much easier to just run a Docker container. In fact, all you need is to run the following command:

sudo docker run -p 7474:7474 -p 7473:7473 -p 7687:7687 neo4j:3.5

Like this, you can avoid bloating your system with dependencies like Java, and just manage the container instead. Depending on the operating system, you use, you may need to keep or drop the sudo command. You’ll also need to mount a volume (not shown here) if you want the data to survive container restarts.

Running a Neo4j 3.5 Docker container.

Once Neo4j 3.5 is running, you can access the Neo4j Browser at localhost:7474:

The Neo4j Browser’s login screen.

Login with the default credentials, i.e. with “neo4j” as both username and password. You will then be prompted to change your password:

Changing password in the Neo4j Browser.

Go ahead and change the password. This is necessary because Cartography would not otherwise be able to connect to Neo4j using the default credentials.

The Neo4j Browser’s dashboard after changing password.

Setting Up a SecurityAudit User in AWS

Cartography can be used to map out several different services, but here we’ll use AWS. To retrieve AWS data, we’ll need to set up a user with a SecurityAudit policy.

Log into the AWS Console, then go into the IAM service, and finally select “Users” on the left. Click the “Add users” button on the right.

Once in IAM, select “Users” on the left, and then click “Add users” on the right.

In the next screen, enter a name for the user, and choose “Access key – Programmatic access” as the AWS credential type, then click the “Next: Permissions” button at the bottom-right.

Enter a username, then choose Programmatic access before proceeding.

In the Permissions screen, select “Attach existing policies directly” (an arguable practice, but for now it will suffice). Use the search input to quickly filter the list of policies until you can see “SecurityAudit”, then click the checkbox next to it, and finally click the “Next: Tags” button at the bottom-right to proceed.

Attach the “SecurityAudit” policy directly to the new user.

There is nothing more to do, so just click on the remaining “Next” buttons and create the user. At this point you are given the new user’s Access key ID and Secret access key. Grab hold of them and keep them in a safe place. We’ll use them shortly.

Now that we have a user with the right permissions, all we need to do us set up the necessary AWS configuration locally, so that Cartography can use that user to inspect the AWS account. This is quite simple and is covered in the AWS Configuration and credential file settings documentation.

First, create a file at ~/.aws/credentials, and then add the Access key ID and Secret access key you just obtained, as follows (replacing the placeholder values):


Then, create another file at ~/.aws/config, and add the basic configuration as follows. I’m not sure whether the region actually makes a difference, since Cartography will in fact inspect all regions for many services that can be deployed in multiple regions.


That’s it! Let’s run Cartography.

Running Cartography

Run the following command to install Cartography:

pip3 install cartography

Then, run Cartography itself:

cartography --neo4j-uri bolt://localhost:7687 --neo4j-password-prompt --neo4j-user neo4j

Enter the Neo4j password you set earlier (i.e. not the default one) when prompted.

Cartography should now run, collecting data from AWS, adding it to Neo4j, and writing output as it works. It takes a while, even for a brand new AWS account.

Querying the Graph

Once Cartography finishes running, go back to the Neo4j Browser at http://localhost:7474/browser/ . You can now write Cypher queries to analyse the data collected by Cartography.

If you haven’t used Cypher before, check out my articles “First Steps with RedisGraph” and “Family Tree with RedisGraph“, as well as my RedisConf 2020 talk “A Practical Introduction to RedisGraph“. RedisGraph is another graph database that uses the same Cypher query language, and these resources should allow you to ramp up quickly.

You might not know what Cartography data to look for initially, but you can always start with a simple MATCH query, and as you type “AWS” as a node type in a partial query (e.g. “MATCH (x:AWS“), Neo4j will suggest types from the ones it knows about. You can also consult the AWS Schema documentation, as well as the aforementioned “Automating Security Visibility and Democratization” talk which illustrates some of these types and their relationships in handy diagrams.

Let’s take a look at a few simple examples around IAM to ease you in.

Example 1: Get All Principals

MATCH (u:AWSPrincipal)

In AWS, a “principal” is an umbrella term for anything that can make a request, including users, groups, roles, and the special root user. Although this is a very basic query, you’ll be surprised by what it returns, including some special internal AWS roles.

Example 2: Get Users with Policies

MATCH (u:AWSUser)-[:POLICY]->(p:AWSPolicy)

This query gets users and their policies via the POLICY relationship. Due to the nature of the query, it won’t return users that don’t have any directly attached policies. In this case all I’ve got is the cartography user I created earlier, but you can see the connection to the SecurityAudit policy.

The cartography user is linked to the SecurityAudit policy.

Example 3: Get Policy Statements for Principals

MATCH (a:AWSPrincipal)-->(p:AWSPolicy)-[:STATEMENT]->(s)
RETURN a, p, s

Cartography parses the statements in AWS policies, so if you inspect a node of type AWSPolicy, you can actually see what resources it provides access to. This query shows the relationship between principals (again, this means users, groups, etc) and the details of the policies attached directly to them.

It is possible to refine this query further to include indirectly assigned policies (e.g. to see what permissions a user has via a group it belongs to), or to look for specific permissions (e.g. whether a principal has access to iam:*).

Results of a Cypher query linking AWS principals to the policy statements that apply to them, via AWS policies.

Wrapping Up

As you can see, Cartography takes a bit of effort to set up and has some caveats, but it’s otherwise a fantastic tool to gather data about your resources into Neo4j for further analysis.

Securing PowerShellGet on a Windows EC2 Instance

I’ve been doing some work with security on AWS recently, and part of that involved running security assessments using Amazon Inspector to identify vulnerabilities at network and host level.

If I launch a fresh EC2 instance right now using the Microsoft Windows Server 2019 Base AMI and run a host-level assessment, the report lists a vulnerability related to the PowerShellGet module:

Microsoft Security Response Center’s entry about this vulnerability explains a little more about it:

“A security feature bypass vulnerability exists in the PowerShellGet V2 module. An attacker who successfully exploited this vulnerability could bypass WDAC (Windows Defender Application Control) policy and execute arbitrary code on a policy locked-down machine.

“An attacker must have administrator privileges to create a configuration that includes installing PowerShellGet V2 module onto a machine from the PowerShell Gallery. The WDAC policy must be configured to allow the module to run. After this is done, PowerShell script can be injected and run fully trusted, allowing the attacker arbitrary code execution on the machine.”

— CVE-2020-16886 at MSRC

The same page says that this vulnerability was fixed in PowerShellGet v. 2.2.5. So why do we have this problem? Here’s why:

PS C:\Users\Administrator> Get-Module PowerShellGet -ListAvailable

    Directory: C:\Program Files\WindowsPowerShell\Modules

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script    PowerShellGet                       {Install-Module, Find-Module, Save-Module, Upda...

PS C:\Users\Administrator>

That AMI came with PowerShellGet, but we need version 2.2.5. We can install it by running a Powershell session in Administrator mode, and running the following commands (from the Installing PowershellGet documentation) and agreeing to install the NuGet provider:

Install-Module -Name PowerShellGet -Force
Update-Module -Name PowerShellGet

This results in the new 2.2.5 version being installed alongside the older one:

A Powershell session showing how we started with PowerShellGet, installed a more recent version, and now have the new 2.2.5 version alongside the old one.

I don’t know enough to be able to say whether having that version around still poses any kind of risk, but it seems to be enough for Amazon Inspector which no longer reports any vulnerability after installing version 2.2.5:

If you’re really paranoid, check out this Stack Overflow question for ways to get rid of the old version manually. I haven’t actually tried this, so be careful.

Retrieving Stock Prices using AWS Lambda

AWS Lambda functions are great for simple logic running periodically (among other things). In this article, we’ll create a simple AWS Lambda function in Python that retrieves stock prices from a REST API every minute. Let’s get straight to it!

Create a Lambda Function

First, we need to create a function. Follow the instructions illustrated below to do this.

From the AWS Console dashboard, locate the Lambda service. You can also do this via the Services drop-down panel at the top, or from your recently visited services (if you’ve already been using Lambda).
Once you are in the Lambda service, create a new function by clicking on the “Create a function” button as shown above.
Choose a name for the Lambda function, and also the runtime. We’re using Python 3.7 (which is the latest supported Python version in AWS Lambda at the time of writing this) for this example, but other options are available (e.g. Node.js, NET Core, Go, etc). Leave everything else as is and hit the Create function button.
Once the Lambda is created, you are taken to the new function itself. A green status message at the top indicates that it has been created successfully.

Editing the Lambda’s Code

The function’s configuration screen can seem quite confusing at first, but all you need to do is scroll down to get to the code editor. While there are a few different ways to add code to your Lambda, using the provided editor (which is the default option for Python) is the easiest.

Replace the default code in the editor with the following, and hit the Save button at the top-right.

from urllib.request import urlopen
from contextlib import closing
import json

def lambda_handler(event, context):
    with closing(urlopen("https://financialmodelingprep.com/api/v3/stock/real-time-price/GOOGL")) as responseData:
        jsonData = responseData.read()
        deserialisedData = json.loads(jsonData)
        price = deserialisedData['price']
    return price

Here we are simply retrieving Google’s stock price using the Financial Modeling Prep Stock Realtime Price API, which is open and doesn’t require any authentication.

Next to the Save button at the top-right, there’s a Test button. Click it, and the following screen comes up.

The Configure test event screen. Just enter a name and hit Create.

Just enter a name (e.g. “Test”) and hit the Create button further below. We’re not using the input JSON data, so you can just ignore it.

Next, click the Test button at the top-right again, and your Lambda function will be executed:

After clicking Test again, the lambda is executed and the results are shown below the code.

The results are shown below the code, and these include various metadata (such as a Request ID and execution time) as well as Google’s stock price of 1082.38, which we retrieved from the REST API and logged using the print statement in the code.

Running Periodically

We now have a working Lambda function, but so far we have to invoke it manually every time. Let’s set it up so that it runs every minute.

At the top of the screen, click CloudWatch Events on the left to add a CloudWatch trigger.

Scroll back to the top, and you’ll see a placeholder telling you to “Add triggers from the list on the left“. Following that advice, click on “CloudWatch Events” to the left.

A CloudWatch Event trigger is added to the function.

This has the effect of adding “CloudWatch Events” as a trigger in the slot where the placeholder text was, but what you might not notice at first is that the lower part of the page changes from the code editor to a “Configure triggers panel“. This can be quite confusing for those new to AWS Lambda who might not intuit right away that clicking on the boxes will affect the content in some other part of the page.

By scrolling down, we can configure the new trigger.

Here we use a Schedule expression of rate(1 minute) to make the function run every minute.

Filling in most of the settings (e.g. choosing a name) is easy, bearing in mind that there are some restrictions (e.g. some characters, such as spaces, are restricted in the name).

The only tricky part is where we specify how frequently we want the function to be executed. For this, we can use cron or rate expressions (refer to AWS documentation: Schedule Expressions Using Rate or Cron). By using an expression of rate(1 minute), we configure the function to run every minute, which is the smallest supported interval.

Once this is all set up, click the Add button to set up the trigger. Then, don’t forget to click the Save button at the top-right of the page to apply the changes to the Lambda function.

Checking Output in CloudWatch

After waiting a few minutes for the function to run a few times, we can go into CloudWatch and check the output of each execution.

CloudWatch logs.

From the AWS Services, locate CloudWatch. Go into Logs from the left menu, and locate the log group for our Lambda function (in this case it’s /aws/lambda/StockChecker).

Select the most recent log stream (the one at the top), and if you scroll to the end, you should see logs showing the function’s execution every minute, as well as whatever we’re writing to standard output (in this case, Google’s stock price).

CloudWatch logs show that the Lambda function is executing every minute.

We can see that the function is executing every minute, and we’re logging a stock price each time. The US stock market is closed right now, and that’s why the stock price is always the same (you’d expect it to change frequently when the market is active).


At this point, we have a simple, working AWS Lambda function (written in Python) that runs every minute and retrieves Google’s stock price. To keep things simple, we’re just writing it to standard output, which means we can see the value in CloudWatch – but we could also expand the code to build something useful from this.

Woodchuck Translation with Amazon Translate

This article is an attempt to have fun with Amazon Translate, and is not intended to be taken as any sort of serious review.

Amazon Web Services (AWS) includes a machine translation service called Amazon Translate:

“Amazon Translate is a neural machine translation service that delivers fast, high-quality, and affordable language translation. Neural machine translation is a form of language translation automation that uses deep learning models to deliver more accurate and more natural sounding translation than traditional statistical and rule-based translation algorithms. Amazon Translate allows you to localize content – such as websites and applications – for international users, and to easily translate large volumes of text efficiently.” — Amazon Translate homepage

Sounds pretty cool. If you log into the AWS Console and select the Amazon Translate service, it gives you an interface where you can easily play with it:

If we hit the “Swap languages” button, this translates the result back to the original language. In this really simple case, it matches perfectly:

However, this is not always the case. Translating back and forth will often result in things being “lost in translation”, for various reasons ranging from context to linguistic differences.

Since we all know machine translation isn’t perfect, I could not resist being a complete bastard and testing AWS Translate against none other than… How much wood would a woodchuck chuck:

Translating back and forth for each of the supported languages yields some interesting and often hilarious results before the translation converges (translating back and forth starts giving you the same thing every time). Let’s take a look at the results of this little experiment.

English <-> German

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Wie viel Holz würde ein Holzfutter, wenn ein Holzfutter Holz abwerfen könnte?
  3. How much wood would a wood fodder if a wood fodder could drop wood?
  4. Wie viel Holz würde ein Holzfutter, wenn ein Holzfutter Holz fallen könnte?
  5. How much wood would a wood lining if a wood lining could fall wood?
  6. Wie viel Holz würde eine Holzverkleidung, wenn eine Holzverkleidung Holz fallen könnte?
  7. How much wood would a wood panelling if a wood paneling could fall wood?

It’s easy to see how, as we translate back and forth, we not only lose the sense of the original question (if it ever had any), but the result also makes no grammatical sense. However, what really catches my eye is the two different spellings of “panelling”/”paneling” in the same phrase.

English <-> French

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Combien de bois un mandrin de bois serait-il un mandrin de bois si un mandrin de bois pouvait mander du bois ?
  3. How much wood would a wood mandrel be a wood mandrel if a wood mandrel could mandrel wood?
  4. Combien de bois un mandrin de bois serait-il un mandrin de bois si un mandrin de bois pouvait mandrin de bois ?
  5. How much wood would a wood chuck be a wood chuck if a wood chuck could wood chuck?

What’s really interesting about this one is that after going on a nice big detour (becoming a “wood mandrel”), the English phrase eventually returns to something relatively close to the original (even if still wrong).

English <-> Portuguese

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Quanta madeira uma marmota manteria se uma marmota pudesse atirar madeira?
  3. How much wood would a groundhog keep if a groundhog could throw wood?

This one’s pretty good – it got the animal right. The only mistake is on the first translation of “chuck” becoming “keep” – you’ll notice that all other translations also interpret the different “chuck”s differently.

English <-> Spanish

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. ¿Cuánta madera haría un Woodchuck si un Woodchuck pudiera tirar madera?
  3. How much wood would a Woodchuck make if a Woodchuck could throw wood?
  4. ¿Cuánta madera ganaría un Woodchuck si un Woodchuck pudiera tirar madera?
  5. How much wood would a Woodchuck gain if a Woodchuck could throw wood?

The Spanish Mr. Woodchuck is a businessman, apparently.

English <-> Arabic

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. كم من الخشب يمكن أن تشوك الخشب إذا كان الخشب يمكن أن يرمي الخشب؟
  3. How much wood can a wood fork if the wood can throw wood?
  4. كم الخشب يمكن شوكة الخشب إذا الخشب يمكن رمي الخشب؟
  5. How much wood can a wood fork if wood can throw wood?

Okay, this is starting to get pretty weird.

English <-> Chinese

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. 如果木头可以木头卡住木头,木头卡住多少木头?
  3. If wood can be stuck in wood, how many wood is stuck?
  4. 如果木头可以被卡在木头上,有多少木头被卡住?
  5. If wood can be stuck on wood, how many wood is stuck?
  6. 如果木头可以卡在木头上,有多少木头被卡住?
  7. If the wood can be stuck on the wood, how many wood is stuck?


English <-> Japanese

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. ウッドチャックが木材をチャックできるなら、どのくらいの木材をチャックしますか?
  3. If a Woodchuck can chuck wood, how much wood would you chuck?
  4. もしウッドチャックが木材をチャックできるなら、どのくらいの木材をチャックしますか?
  5. If the wood chuck can chuck wood, how much wood would you chuck?
  6. 木材チャックが木材をチャックできるなら、どれくらいの木材をチャックしますか?
  7. If a wood chuck can chuck wood, how much wood do you chuck?
  8. 木材チャックが木材をチャックできる場合、どれくらいの木材をチャックしますか?
  9. If a wood chuck can chuck wood, how much wood do you chuck?

I don’t know… how much wood would you chuck?

English <-> Russian

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Сколько дров будет завалить, если дрочак сможет заправлять древесину?
  3. How much wood will it fill if the wok can fill the wood?
  4. Сколько древесины она заполнит, если вок сможет заполнить дерево?
  5. How much wood will it fill if the wok can fill the tree?
  6. Сколько дерева она заполнит, если вок сможет заполнить дерево?
  7. How much tree would she fill if the wok could fill the tree?

Image credit: source

Sounds like one of Google’s interview questions.

English <-> Traditional Chinese

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. 如果一個木頭能扔木頭的話一個木頭會扔多少木頭
  3. How much wood would a wood throw if one could throw wood?
  4. 如果能扔木頭的話木頭會扔多少木頭
  5. How much wood would it throw out if we could throw it?
  6. 如果我們能扔掉的話會丟多少木頭
  7. How much wood would we lose if we could throw it away?
  8. 如果我們能把它扔掉我們會損失多少木頭
  9. How much wood would we lose if we could get rid of it?
  10. 如果能除掉它我們會損失多少木頭
  11. How much wood would we lose if we could get rid of it?

I think we’ve found the Spanish woodchuck businessman’s rival.

English <-> Italian

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Quanta legna manderebbe una marmotta se una marmotta potesse buttare legna?
  3. How much wood would a groundhog send if a groundhog could throw out wood?
  4. Quanto legna manderebbe una marmotta se una marmotta potesse buttare la legna?
  5. How much wood would a groundhog send if a groundhog could throw out the wood?
  6. Quanto legna manderebbe una marmotta se una marmotta potesse buttare via la legna?
  7. How much wood would a groundhog send if a groundhog could throw away the wood?

This one is interesting as there are a lot of very subtle changes before convergence.

English <-> Turkish

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Eğer bir dağ sıçanı odunları çöpe atabilseydi, bir dağ sıçanı ne kadar ağaç gönderirdi?
  3. If a groundhog could throw wood away, how many trees would a groundhog send?
  4. Eğer bir dağ sıçanı tahta atabilseydi, bir dağ sıçanı kaç ağaç gönderirdi?
  5. If a groundhog could throw a throne, how many trees would a groundhog send?
  6. Eğer bir dağ sıçanı tahtı atabilseydi, bir dağ sıçanı kaç ağaç gönderirdi?
  7. If a groundhog could throw the throne, how many trees would a groundhog send?

It is really bizarre to see how “wood” transitions into “throne” and “trees” in two different parts of the same question.

English <-> Czech

  1. How much wood would a woodchuck chuck if a woodchuck could chuck wood?
  2. Kolik dřeva by dřevorubec sklízl, kdyby dřevorubec mohl sklíčit dřevo?
  3. How much wood would a lumberjack harvest if a lumberjack could deceive the wood?
  4. Kolik dřeva by dřevorubec sklízel, kdyby dřevorubec mohl klamat dřevo?
  5. How much wood would a lumberjack harvest if a lumberjack could deceive wood?

Image credit: source


I had fun playing around with Amazon Translate and seeing how the woodchuck tongue-twister degenerates when translated across different languages. I hope it was just as much fun for you to read this.

Please do not make any judgements about the accuracy of Amazon Translate based on this, for the following reasons:

  1. This is a very specific case and certainly doesn’t speak for the accuracy across entire languages.
  2. Translation isn’t easy. We’ve all heard of situations where things got “lost in translation”. Translation depends very much on context and linguistic differences. Hopefully the varying performance across languages is an illustration of this.
  3. Machine translation isn’t easy either. There’s a reason why it’s considered a field of artificial intelligence.

Spinning up a Windows Virtual Machine in AWS

In this article, we’ll go through all the steps necessary to set up a basic Windows virtual machine (VM) in Amazon Web Services (AWS).

In AWS, the service used to manage VMs is called Elastic Compute Cloud (EC2). Thus, the first thing we need to do is access the EC2 service from the AWS Console homepage:

This brings us to the EC2 dashboard. We can click Instances in the left menu to get to the page where we can manage our VMs (note that we can also launch a VM / EC2 Instance directly from here):

The Instances page lists any VMs that we already manage, and allows us to launch new ones. Click on one of the Launch Instance buttons to create a new VM:

The next step is to select something called the Amazon Machine Image (AMI). This basically means what operating software and software you want to have on the VM. In our case, we’ll just go for the latest Windows image available:

The next thing to choose is the instance type. Virtual machines on AWS come in many shapes and sizes – some are general-purpose, whereas others are optimised for CPU, memory, or other resources. In our case we don’t really care, so we’ll just go for the general-purpose t2.micro, which is also free tier eligible:

Since we’re just getting started and don’t want to get lost in the details of complex configuration, we’ll just Review and Launch. This brings us to the review page where we can see what we are about to create, and we can subsequently launch it:

One thing to note in this page is that the instance launch wizard will create, aside from the EC2 instance (VM) itself, a security group. Let’s take note of this for now – we’ll get back to it in a minute. Hit the Launch button.

Before the VM is spun up, you are prompted to create or specify a key pair:

A key pair is needed in order to gain access to the VM once it is launched. You can use an existing key pair if you have one already; otherwise, select “Create a new key pair” from the drop-down list. Specify a name for the key pair, and download it. This gives you a .pem file which you will need soon, and also allows you to finally launch the instance.

Once you hit the Launch Instances button, the VM starts to spin up. It may take a few minutes before it is available.

Scroll down and use the View Instances button at the bottom right to go back to the EC2 Instances page. There, you can see the new VM that should be in a running state. By selecting the VM, you can see its Public DNS name, which you can use to remote into the VM (though we’ll see an easier way to do this in a minute):


Before we can remote into the machine, it needs to have its RDP port open. We can go to the Security Groups page to see the security group for the VM we created – remember that the instance launch wizard created a security group for us:

As you can see, the VM’s security group is already configured to allow RDP from anywhere, so no further action is needed. However, in a real system, this may pose a security risk and should be restricted.

Back in the Instances page, there is a Connect button that gives us everything we need to remote into the Windows VM we have just launched:

From here, we can download a .rdp file which allows us to remote into the machine directly instead of having to specify its DNS name every time. It also shows the DNS name (in case we want to do that anyway), and provides the credentials necessary to access the machine. The username is Administrator; for the password, we need to click the Get Password button and go through an additional step:

The password for the machine can be retrieved by locating the .pem file (downloaded earlier when we created the key pair) and clicking on the Decrypt Password button. Note that you may need to wait a few minutes from instance launch before you can do this.

The password for the machine is now available and can be copied:

Now that we have everything we need, let’s remote into the VM. Locate the .rdp file downloaded earlier, and run it:

You are then prompted for credentials:

By default, Windows will try to use your current ones, so opt to “Use a different account” and specify the credentials of the machine retrieved in the earlier steps.

Bypass the security warning (we’re grown-ups, and know what we’re doing… kind of):

And… we’re in!

If you’re not planning to use the VM, don’t forget to stop or terminate it to avoid incurring unnecessary charges:

The VM will sit there in Terminated state for a while before going away permanently.