The relationship linker¶
RelationshipLinker(assets, materialize_external=True) subclasses IdentifierIndex.
7.1 The identifier index¶
Every asset is registered under:
- its
arnand itsname; - the native short ID: the last
/or:segment ofarn(not fork8s://,k8s-gke://,gcp-principal:,entra:,loganalytics:identifiers, whose tails are not IDs); - known self-ID metadata keys:
group_id,route_table_id,internet_gateway_id,nat_gateway_id,network_interface_id,volume_id,allocation_id,network_acl_id,peering_connection_id,transit_gateway_id,vpc_endpoint_id,launch_template_id,file_system_id,queue_url,repository_uri,zone_id; - every entry of
metadata.aliases; - type-specific keys:
vpc_id(VPC / VNet),subnet_id(subnet),public_ip(Elastic IP, EC2),account_id(account nodes); - names other services use by domain:
<bucket>.s3.amazonaws.com, a load balancer'sdns_name(as-is and lower-cased without the trailing dot), a distribution'sdomain_name.
An identifier that starts with /subscriptions/ or /providers/, or contains a dot and no
slash (host names), is also registered lower-cased, so Azure IDs and DNS names match
case-insensitively.
7.2 Resolution¶
resolve(identifier, context=None) returns an asset id or None:
- Exact lookup.
- Each variant of the identifier (
_variants), in order: trimmed of whitespace, trailing.and/; lower-cased; without adualstack.prefix; without a trailing:*; a Lambda ARN without its version / alias qualifier; an S3 object ARN reduced to the bucket ARN; an image reference reduced to its repository (for ECR hosts, matched by a full-match regular expression on the parsed registry host, and for anyhost/repo:tag); an API Gateway stage ARN reduced to the API; an Azure Container Registry image reduced to the registry login server. - A role ARN that missed (built from a bare role name, while the real role has a path): the role with that name in that account.
- An STS assumed-role ARN: the role with that name in that account.
When several assets share an identifier, _pick chooses:
- drop placeholders when a real asset is among the candidates;
- a single candidate wins;
- globally unique identifier schemes (
arn:,/subscriptions/,/providers/,//,k8s://,cloudg:,aws-security:,azure-security:,entra:,loganalytics:,gcp-principal:,k8s-gke://) take the first candidate; - without context, the first candidate;
- a candidate in the referencing asset's account and region;
- a candidate in the same account;
- exactly one candidate in region
global; - otherwise no match: an ambiguous name such as
defaultthat exists in several accounts is left unresolved rather than guessed.
7.3 External accounts and unresolved references¶
When a declared target does not resolve, _external_account() checks whether it belongs to an
account that was not mapped: an AWS ARN with an account field, a bare 12-digit account ID, an
Azure ID under /subscriptions/<guid>, a GCP name under //….googleapis.com/projects/<id>. If
the account differs from the referencing asset's, a CLOUD_ACCOUNT placeholder with
external: true is created once per account (and indexed, so later references reuse it), and
the edge points there with properties.external_reference set to the original identifier.
AWS-owned references (account aws, as in AWS managed policies) are not externalised.
Anything else is recorded in unresolved (up to 2000 entries).
7.4 The three passes¶
link(include_generic=True) runs, each pass wrapped so that one malformed asset cannot stop the
run (failures are logged at debug level):
- Declared relations (
_link_declared). For each relation: parseedge(unknown →REFERENCES), resolvetargetwith the asset as context, fall back to an external account, else record as unresolved.reverseswaps source and target. Properties are copied. - Provider rules (
_link_rules, inlinker_rules.py), for metadata conventions that predate declared relations or are shared by many collectors:security_groups[],nsg_id,vpc_config.SecurityGroupIds→ATTACHED_TO / PROTECTED_BY_SG;subnet_id,vpc_config.SubnetIds→ subnetCONTAINS / SUBNET_CONTAINS_INSTANCE;raw_data.Roleorrole_arn→ASSUMES_ROLE / RUNS_ONfor functions and state machines,REFERENCESotherwise;raw_data.IamInstanceProfile.Arn→ASSUMES_ROLE / RUNS_ON;kms_key_id→REFERENCES / ENCRYPTED_BY_KMS;- CloudFront
origins→REFERENCES / SERVES_TRAFFIC_TO(bucket origins resolved through their.s3host; skipped when a typed relation already links the pair);web_acl_id→ WAFPROTECTS / PROTECTED_BY_WAF; - route tables: each route's gateway / NAT / transit gateway / peering / ENI target →
ROUTE(NAT_TRANSLATEDorTRANSIT_ROUTED); associations → subnetATTACHED_TO; attachments[].VpcId→ATTACHED_TO;attached_instance_id(s),network_interface_id→ATTACHED_TO;- peering connections → requester and accepter VPCs
PEERING / VPC_PEERED; - Azure
network_interfaces→ NICATTACHED_TOVM; - GCP
parent_full_resource_name→ parentCONTAINS. Then the SSO role rule (_link_sso_roles): an Identity Center permission set withprovisioned_role_prefixMANAGES / OWNED_BYevery IAM role under/aws-reserved/sso.amazonaws.com/named<prefix><16 hex characters>.
- Generic references (
_link_generic, withinclude_generic, frominventory.link_references). Walks every string in the asset's metadata (depth 6), exceptrelations,aliases,assume_role_policyand the self-ID keys. A string that looks like an identifier (arn:…,/subscriptions/…, a GCP full resource name, or an AWS short ID such asvpc-…,sg-…,snap-…) and resolves to another asset becomesREFERENCES, unless the pair is already linked in either direction. This catches relationships no rule knows about.
Deduplication: one edge per (source, target, edge_type) across all passes and seeded edges;
self-loops are dropped.