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:
- 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.
- It gives the feeling that Cartography is not very well-maintained, if issues as important as this take well over a year to resolve.
- 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.
Once Neo4j 3.5 is running, you can access the Neo4j Browser at localhost:7474:
Login with the default credentials, i.e. with “neo4j” as both username and password. You will then be prompted to change your password:
Go ahead and change the password. This is necessary because Cartography would not otherwise be able to connect to Neo4j using the default credentials.
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.
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.
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.
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):
[default] aws_access_key_id=ACCESSKEYIDVALUE aws_secret_access_key=SECRETACCESSKEYIDVALUE
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.
[default] region=us-west-2 output=json
That’s it! Let’s run 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) RETURN u
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) RETURN u, p
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.
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
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.