How to Build an Open Source ASM for Attack Surface Management with CloudQuery and Neo4j

Jason Kao
Name
Jason Kao
Twitter
kaojason

In this guide, we will demonstrate how to set up CloudQuery for customizable Attack Surface Management (ASM) and how to get started with utilizing graph visualization to execute security queries. With cloud infrastructure and security, graph visualization can be used effectively to understand relationships between assets, attack paths, and attack surface to aid with Attack Surface Management and improving security posture of your organization.

Once we're done with the post, you'll be able to create and build your own Attack Surface Management and Graph Visualization queries and relationships in Neo4j similar to the example graph of AWS IAM Users, their permissions, and IAM user access keys. We'll show how to build the queries and relationships for the below graph visualization and other examples.

Example visualization of IAM Users

Walkthrough

Prerequisites

In this guide, we will use Neo4j as a destination and AWS as a source. For more information on how to set those up, see our documentation on Neo4j (opens in a new tab) and AWS (opens in a new tab).

For the example queries, we'll be using AWS Identity and Access Management (IAM) Users and Amazon Relational Database Service (RDS) along with some common infrastructure associated with those resources that include IAM User Access Keys, IAM Managed and Inline Policies, IAM Groups, Amazon Virtual Private Cloud (VPC), Amazon VPC Security Groups, and Internet Gateways. Our demo environment will have those sample resources already created and the graph visualizations will look different than results from your walkthrough.

Refer to Neo4j's installation documentation (https://neo4j.com/docs/operations-manual/current/installation/ (opens in a new tab)) for help setting up Neo4j. For this walkthrough, make sure a local instance of Neo4j is up and running. Also make sure to install Awesome Procedures on Cypher (APOC) (opens in a new tab) for Neo4j as we'll be using useful functionality in APOC to assist with our attack surface management use cases.

Step 1: Install or Deploy CloudQuery

You can get started with CloudQuery without any installation or deployment using our Managed Syncs.

To use CloudQuery locally, check out our quickstart guide and AWS source plugin (opens in a new tab) for how to configure CloudQuery.

Step 2: Sync Data from CloudQuery to Neo4j

The following command will sync data from AWS as a source to Neo4J as a destination:

cloudquery sync aws-neo.yml neo4j.yml

For more information on configuration files, see AWS source configuration (opens in a new tab) and Neo4j destination configuration (opens in a new tab)

The configuration we're running locally looks like this:

Example aws-neo.yml source configuration file:

kind: source
spec:
  name: "aws"
  path: "cloudquery/aws"
  registry: "cloudquery"
  version: "v25.2.2"
  destinations: ["neo4j"]
  tables: ["*"]
  spec:
    regions: 
      - us-east-1
    accounts:
      - id: "123412341234" #Your AWS Account Number here
      - local_profile: "test-neo4j-example"

Example neo4j.yml destination configuration file:

  kind: destination
  spec:
    name: "neo4j"
    registry: "cloudquery"
    path: "cloudquery/neo4j"
    version: "v5.1.3"
    spec:
      connection_string: "bolt://localhost:7687"
      username: "${USERNAME}"
      password: "${PASSWORD}"

You should see a sync completed successfully message. CloudQuery has now synced your AWS data to Neo4j.

Step 3: Test Out Neo4j

If running Neo4j locally, navigate to http://localhost:7474/browser/ for the Neo4j browser.

Let's start with a simple query to find all our IAM Roles.

MATCH (n:aws_iam_roles) RETURN n

IAM Roles

Step 4: Run Custom ASM Queries and Create Relationships in Neo4j

Example 1: IAM User Access Keys and their linked permissions

Let's start with IAM User Access Keys. With this query, we'll look for the 4 distinct ways with identity policies an IAM User can be granted permissions and link those to the IAM Users and to the IAM User Access Keys. In this example, we've already created the following resources in AWS: IAM Users, IAM Groups, Inline Policies for both Groups and Users, Managed Policies for both Groups and Users, and IAM User Access Keys.

An IAM User can be granted permissions from:

  • Direct Inline Policies of the IAM User
  • Directly Attached Managed Policies to the IAM User
  • Inline Policies via IAM Group Membership
  • Attached Managed Policies via IAM Group Membership

By running the following command, we create a relationship between IAM User nodes and IAM User Access Key nodes.

MATCH
  (a:aws_iam_user_access_keys),
  (b:aws_iam_users)
WHERE a.`_cq_parent_id` =  b.`_cq_id`
CREATE (b)-[r:has_access_keys]->(a)
RETURN type(r)

Next, let's create a relationship between IAM Users and IAM Groups. In AWS, IAM Users can be members of IAM Groups and inherit their IAM policies.

MATCH
  (iamusers:aws_iam_users),
  (usergroups:aws_iam_user_groups),
  (iamgroups:aws_iam_groups)
WHERE iamusers.arn = usergroups.user_arn and iamgroups.arn = usergroups.arn
CREATE (iamusers)-[r:is_a_member_of]->(iamgroups)
RETURN type(r)

Now, we'll create relationships between IAM Users and all IAM User inline policies.

MATCH 
  (iamusers:aws_iam_users),
  (inlinep:aws_iam_user_policies)
WHERE iamusers.arn = inlinep.user_arn
CREATE (iamusers)-[r:has_inline_policy]->(inlinep)
RETURN type(r)

Now, we'll create relationships between IAM Users and directly attached managed policies.

MATCH 
  (iamusers:aws_iam_users), 
  (attachp:aws_iam_user_attached_policies)
WHERE iamusers.arn = attachp.user_arn
CREATE (iamusers)-[r:has_attached_policy]->(attachp)
RETURN type(r)

Next, we'll create relationships between IAM Groups and their inline policies.

MATCH 
  (iamgroups:aws_iam_groups),
  (groupinline:aws_iam_group_policies)
WHERE iamgroups.arn = groupinline.group_arn
CREATE (iamgroups)-[r:has_inline_policy]->(groupinline)
RETURN type(r)

Lastly, we'll create relationships between IAM Groups and their attached managed policies.

MATCH 
  (n:aws_iam_groups), 
  (policies:aws_iam_policies) 
UNWIND (keys(apoc.convert.fromJsonMap(n.policies))) as y 
WITH y, policies, n
WHERE y = policies.arn
CREATE (n)-[r:has_attached_policy]->(policies)
RETURN type(r)

After all these relationships have been created, we can run a MATCH (n:aws_iam_users) return n to return all IAM Users.

In the UI, feel free to play around with node labels, colors, and expansion of nodes and relationships.

In our sample environment, we have 3 IAM Users. The following image shows the following and their relationships:

  • IAM User Access Keys in green.
  • IAM Managed Policies and Inline Policies in red.
  • IAM Users in gray.
  • IAM Groups in blue.

We'll use the following query to show our IAM Users: MATCH (n:aws_iam_users) return n:

Sample Graph of IAM Users

Example 2: Data in RDS

In this example, we will focus on Relational Databases (AWS RDS) and their data. This will include network connectivity such as VPCs, Internet Gateways, and Security Groups. We will also look at possible data access and encryption via KMS Keys, their KMS Key Policies, and KMS Key Grants. In this example, we've already created the following resources in AWS: RDS Databases, KMS Keys, KMS Key Policies, KMS Key Grants, an AWS VPC, Security Groups, and an Internet Gateway.

Let's first create relationships between RDS instances and their encryption from KMS Keys.

MATCH 
  (rdsinstances:aws_rds_instances), 
  (kmskeys:aws_kms_keys)
WHERE rdsinstances.kms_key_id = kmskeys.arn
CREATE (rdsinstances)-[r:is_encrypted_by_key]->(kmskeys)
RETURN type(r)

Let's connect KMS Keys with all their Key Grants and the access that the Key Grants may permit to those KMS Keys and data.

MATCH 
  (keygrants:aws_kms_key_grants),
  (kmskeys:aws_kms_keys)
WHERE keygrants.key_arn = kmskeys.arn
CREATE (kmskeys)-[r:has_kms_key_grant]->(keygrants)
RETURN type(r)

Let's now link Security Groups to the RDS instances.

MATCH 
  (rds_is:aws_rds_instances), 
  (sgs:aws_ec2_security_groups) 
UNWIND (apoc.convert.fromJsonList(rds_is.vpc_security_groups)) as secgroups
WITH secgroups, rds_is, sgs
WHERE
  secgroups['VpcSecurityGroupId'] 
  = sgs.group_id 
CREATE (rds_is)-[r:uses_security_group]->(sgs) 
RETURN type(r)

In the next cypher query, we will connect KMS Keys with their KMS Key Policies.

MATCH 
  (keypolicies:aws_kms_key_policies),
  (keys:aws_kms_keys) 
WHERE keypolicies.key_arn = keys.arn
CREATE (keys)-[r:has_key_policy]->(keypolicies)
RETURN type(r)

Now, we'll create relationships between RDS Instances and the VPC networks they belong to.

MATCH 
  (rds_is:aws_rds_instances), 
  (vpcs:aws_ec2_vpcs) 
WHERE apoc.convert.fromJsonMap(rds_is.db_subnet_group)['VpcId'] = vpcs.vpc_id
CREATE (rds_is)-[r:is_in_vpc]->(vpcs)
return type(r)

Lastly, we'll create relationships between Internet Gateways and their VPCs. In this cypher query, we use [0] to pull the first item in the list. Internet Gateways can only be attached to 1 VPC, so this will return the only attachment in the attachment properties of the Internet Gateway resource.

MATCH 
  (igws:aws_ec2_internet_gateways), 
  (vpcs:aws_ec2_vpcs)
WHERE apoc.convert.fromJsonList(igws.attachments)[0]['VpcId'] = vpcs.vpc_id
CREATE (vpcs)-[r:has_internet_gateway]->(igws)
return type(r)

We've now created relationships for encryption and networking for our RDS instances.
In our sample environment, we have 4 RDS Instances. The following image shows the following and their relationships:

  • RDS Instances in yellow.
  • VPCs in blue.
  • Security Groups in green.
  • Internet Gateways in light blue.
  • KMS Keys in orange.
  • KMS Key Policies in light pink.
  • KMS Key Grants in dark pink.

To display our graph, the nodes, and relationships, we will use MATCH (n:aws_rds_instances) return n; to return a graph visualization of our data.

Sample Graph of RDS Instances

Summary

We have demonstrated how to get started with Attack Surface Management (ASM) and graph visualization with Neo4j along with some starter queries regarding databases, data, and AWS Identity and Access Management. Now you should be able to customize and create more queries, relationships, and utilize CloudQuery to help improve the security posture of your organization!

If you have use cases, custom queries, and examples from using CloudQuery, we would love to hear from you! Reach out to us on GitHub (opens in a new tab) or Discord (opens in a new tab)!