cartography cartography cartography
/

Basic Use

  • Quick start: Install and Run Cartography On Test Machine
  • Usage
    • Usage Tutorial
    • How to use Drift-Detection
    • Building around Cartography
    • Sample queries
    • Cartography Schema
  • Cartography Production Operations

Intel Modules

  • Airbyte
    • Airbyte Configuration
    • Airbyte Schema
  • Anthropic
    • Anthropic Configuration
    • Anthropic Schema
  • Amazon Web Services (AWS)
    • AWS Configuration
    • Permissions Mapping
    • AWS Schema
  • Microsoft Azure
    • Azure Configuration
    • Azure Schema
  • BigFix
    • BigFix Configuration
    • BigFix Schema
  • Cloudflare
    • Cloudflare Configuration
    • Cloudflare Schema
  • Crowdstrike
    • Crowdstrike Configuration
    • Crowdstrike Schema
  • CVE
    • CVE Configuration
    • CVE Schema
  • DigitalOcean
    • Configuration
    • DigitalOcean Schema
  • Duo
    • Duo Configuration
    • Duo Schema
  • Microsoft Entra (formerly Azure AD)
    • Entra Configuration
    • Entra Schema
    • Example Queries
  • Google Cloud Compute (GCP)
    • GCP Configuration
    • GCP Schema
  • Github
    • Github Configuration
    • Github Schema
  • Google GSuite
    • GSuite Configuration
    • Method 2: Using OAuth
    • GSuite Schema
  • Jamf
    • Jamf Schema
  • Kandji
    • Kandji Configuration
    • Kandji Schema
  • Kubernetes
    • Kubernetes Configuration
    • Kubernetes Schema
  • Lastpass
    • Lastpass Configuration
    • Lastpass Schema
  • Oracle Cloud Infrastructure
    • OCI Config
    • OCI Schema
  • Okta
    • Okta Configuration
    • Okta Schema
  • OpenAI
    • OpenAI Configuration
    • OpenAI Schema
  • PagerDuty
    • Pagerduty Configuration
    • Pagerduty Schema
  • Scaleway
    • Scaleway Configuration
    • Scaleway Schema
  • Semgrep
    • Semgrep Configuration
    • Semgrep Schema
  • SnipeIT
    • SnipeIT Configuration
    • SnipeIT Schema
  • Tailscale
    • Tailscale Configuration
    • Tailscale Schema
  • Trivy
    • Trivy Configuration
    • Notes on running Trivy
    • Trivy Schema

Development Docs

  • Cartography Developer Guide
  • How to write a new intel module
  • How to extend Cartography with Analysis Jobs

Get In Touch

  • Contact
  • Community Meeting

On this page

  • Example
cartography-cncf/cartography 0 0
Edit this page
  1. cartography /
  2. MatchLinks

MatchLinks¶

MatchLinks are a way to create relationships between two existing nodes in the graph.

Example¶

Suppose we have a graph that has AWSPrincipals and S3Buckets. We want to create a relationship between an AWSPrincipal and an S3Bucket if the AWSPrincipal has access to the S3Bucket.

Let’s say we have the following data that maps principals with the S3Buckets they can read from:

  1. Define the mapping data

    mapping_data = [
        {
            "principal_arn": "arn:aws:iam::123456789012:role/Alice",
            "bucket_name": "bucket1",
            "permission_action": "s3:GetObject",
        },
        {
            "principal_arn": "arn:aws:iam::123456789012:role/Bob",
            "bucket_name": "bucket2",
            "permission_action": "s3:GetObject",
        }
    ]
    
  2. Define the MatchLink relationship between the AWSPrincipal and the S3Bucket

    @dataclass(frozen=True)
    class S3AccessMatchLink(CartographyRelSchema):
        rel_label: str = "CAN_ACCESS"
        direction: LinkDirection = LinkDirection.OUTWARD
        properties: S3AccessRelProps = S3AccessRelProps()
        target_node_label: str = "S3Bucket"
        target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
            {'name': PropertyRef('bucket_name')},
        )
    
        # These are the additional fields that we need to define for a MatchLink
        source_node_label: str = "AWSPrincipal"
        source_node_matcher: SourceNodeMatcher = make_source_node_matcher(
            {'principal_arn': PropertyRef('principal_arn')},
        )
    

    This is a standard CartographyRelSchema object as described in the intel module guide, except that now we have defined a source_node_label and a source_node_matcher.

  3. Define a CartographyRelProperties object with some additional fields:

    @dataclass(frozen=True)
    class S3AccessRelProps(CartographyRelProperties):
        # <Mandatory fields for MatchLinks>
        lastupdated: PropertyRef = PropertyRef("UPDATE_TAG", set_in_kwargs=True)
    
        # Cartography syncs objects account-by-account (or "sub-resource"-by-"sub-resource")
        # We store the sub-resource label and id on the relationship itself so that we can
        # clean up stale relationships without deleting relationships defined in other accounts.
        _sub_resource_label: PropertyRef = PropertyRef("_sub_resource_label", set_in_kwargs=True)
        _sub_resource_id: PropertyRef = PropertyRef("_sub_resource_id", set_in_kwargs=True)
        # </Mandatory fields for MatchLinks>
    
        # Add in extra properties that we want to define for the relationship
        # For example, we can add a `permission_action` property to the relationship to track the action that the principal has on the bucket, e.g. 's3:GetObject'
        permission_action: PropertyRef = PropertyRef("permission_action")
    
  4. Load the matchlinks to the graph

    load_matchlinks(
        neo4j_session,
        S3AccessMatchLink(),
        mapping_data,
        UPDATE_TAG=UPDATE_TAG,
        _sub_resource_label="AWSAccount",
        _sub_resource_id=ACCOUNT_ID,
    )
    

    This function automatically creates indexes for the nodes involved, as well for the relationship between them (specifically, on the update tag, the sub-resource label, and the sub-resource id fields).

  5. Run the cleanup to remove stale matchlinks

    cleanup_job = GraphJob.from_matchlink(matchlink, "AWSAccount", ACCOUNT_ID, UPDATE_TAG)
    cleanup_job.run(neo4j_session)
    
  6. Enjoy! matchlinks

A fully working (non-production!) test example is here:

from dataclasses import dataclass
import time

from neo4j import GraphDatabase
from cartography.client.core.tx import load_matchlinks
from cartography.graph.job import GraphJob
from cartography.models.core.common import PropertyRef
from cartography.models.core.relationships import (
        CartographyRelProperties,
        CartographyRelSchema,
        LinkDirection,
        SourceNodeMatcher,
        TargetNodeMatcher,
        make_source_node_matcher,
        make_target_node_matcher,
    )


@dataclass(frozen=True)
class S3AccessRelProps(CartographyRelProperties):
    # <Mandatory fields for MatchLinks>
    lastupdated: PropertyRef = PropertyRef("UPDATE_TAG", set_in_kwargs=True)
    _sub_resource_label: PropertyRef = PropertyRef("_sub_resource_label", set_in_kwargs=True)
    _sub_resource_id: PropertyRef = PropertyRef("_sub_resource_id", set_in_kwargs=True)
    # </Mandatory fields for MatchLinks>

    permission_action: PropertyRef = PropertyRef("permission_action")

@dataclass(frozen=True)
class S3AccessMatchLink(CartographyRelSchema):
    rel_label: str = "CAN_ACCESS"
    direction: LinkDirection = LinkDirection.OUTWARD
    properties: S3AccessRelProps = S3AccessRelProps()
    target_node_label: str = "S3Bucket"
    target_node_matcher: TargetNodeMatcher = make_target_node_matcher(
        {'name': PropertyRef('bucket_name')},
    )
    source_node_label: str = "AWSPrincipal"
    source_node_matcher: SourceNodeMatcher = make_source_node_matcher(
        {'principal_arn': PropertyRef('principal_arn')},
    )

mapping_data = [
    {
        "principal_arn": "arn:aws:iam::123456789012:role/Alice",
        "bucket_name": "bucket1",
        "permission_action": "s3:GetObject",
    },
    {
        "principal_arn": "arn:aws:iam::123456789012:role/Bob",
        "bucket_name": "bucket2",
        "permission_action": "s3:GetObject",
    }
]


if __name__ == "__main__":
    UPDATE_TAG = int(time.time())
    ACCOUNT_ID = "123456789012"

    driver = GraphDatabase.driver("bolt://localhost:7687", auth=None)
    with driver.session() as neo4j_session:
        neo4j_session.run("MATCH (n) DETACH DELETE n")

        # Account 123456789012 has principals p1 and p2, and buckets b1, b2, b3.
        neo4j_session.run("""
        MERGE (acc:AWSAccount {id: $account_id, lastupdated: $update_tag})
        MERGE (p1:AWSPrincipal {principal_arn: "arn:aws:iam::123456789012:role/Alice", name:"Alice", lastupdated: $update_tag})
        MERGE (acc)-[res1:RESOURCE]->(p1)

        MERGE (p2:AWSPrincipal {principal_arn: "arn:aws:iam::123456789012:role/Bob", name:"Bob", lastupdated: $update_tag})
        MERGE (acc)-[res2:RESOURCE]->(p2)

        MERGE (b1:S3Bucket {name: "bucket1", lastupdated: $update_tag})
        MERGE (acc)-[res3:RESOURCE]->(b1)

        MERGE (b2:S3Bucket {name: "bucket2", lastupdated: $update_tag})
        MERGE (acc)-[res4:RESOURCE]->(b2)
        SET res1.lastupdated = $update_tag, res2.lastupdated = $update_tag, res3.lastupdated = $update_tag, res4.lastupdated = $update_tag
        """, update_tag=UPDATE_TAG, account_id=ACCOUNT_ID)

        load_matchlinks(
            neo4j_session,
            S3AccessMatchLink(),
            mapping_data,
            UPDATE_TAG=UPDATE_TAG,
            _sub_resource_label="AWSAccount",
            _sub_resource_id=ACCOUNT_ID,
        )
        cleanup_job = GraphJob.from_matchlink(S3AccessMatchLink(), "AWSAccount", ACCOUNT_ID, UPDATE_TAG)
        cleanup_job.run(neo4j_session)

2021-2025, The Linux Foundation

Made with Sphinx and Shibuya theme.