Skip to main content

ADR-015: AWS Source System Tenancy Scheme

Status

Accepted (2026-03-11)


Context

The SecurityV0 entity model uses source_system to identify which connector produced an entity. Existing values are flat strings: entra_id, servicenow, azure_foundry. These work because each connector instance connects to exactly one logical tenant (one Entra directory, one ServiceNow instance).

AWS is structurally different:

  • A single enterprise customer may have dozens of AWS accounts (prod, dev, staging, per-team, per-region sandbox accounts) all visible to the same connector configuration
  • A single SecurityV0 deployment may serve multiple unrelated customers, each with their own AWS accounts
  • AWS entity ARNs encode account ID: arn:aws:lambda:eu-west-1:123456789012:function:my-fn — the account ID is part of the canonical identifier

Without account-scoped source_system values, the platform cannot distinguish:

  • Lambda Function "my-fn" in account 123456789012 from Lambda Function "my-fn" in account 987654321098
  • A cross-account authority path (intentional, should trigger deep_trust_chain finding) from a path within a single account (normal, no finding)
  • An Inetum Lambda from a different customer's Lambda sharing the same function name

The cross-connector correlation problem

The cross-connector entity correlation research (2026-02-26) established that entities are correlated across connectors using external_id. For AWS entities, external_id is the full ARN (e.g., arn:aws:iam::123456789012:role/my-role). The ARN already encodes account scope, so external_id uniqueness is guaranteed.

However, source_system is used in:

  • Entity deduplication (same source_system + external_id = same entity)
  • API filtering (GET /entities?source_system=aws_lambda)
  • UI source badges (the badge label and icon in authority path detail views)
  • Connector scan scheduling (which connector instance handles which source_system)

If source_system is flat (aws_lambda), two entities with the same function name in different accounts would collide at deduplication if the ARN-based external_id is not checked. More critically, the scan scheduler cannot route "rescan account 123456789012" without an account-scoped identifier.


Decision

Use {service}:{account_id} as the source_system value for all AWS entities.

Format

aws_{service}:{account_id}

Examples:

aws_lambda:123456789012
aws_iam:123456789012
aws_ecr:123456789012
aws_ecs:123456789012
aws_s3:123456789012
aws_secretsmanager:123456789012
aws_bedrock:123456789012
aws_orgs:123456789012

For entities that span accounts (e.g., AWS Organizations SCPs which apply org-wide), use the management account ID:

aws_orgs:management_account_id

Entity examples

Entity(
type="workload",
subtype="lambda_function",
external_id="arn:aws:lambda:eu-west-1:123456789012:function:provision-user",
source_system="aws_lambda:123456789012",
display_name="provision-user",
tenant_id="inetum-prod"
)

Entity(
type="identity",
subtype="iam_role",
external_id="arn:aws:iam::123456789012:role/LambdaExecutionRole",
source_system="aws_iam:123456789012",
display_name="LambdaExecutionRole",
tenant_id="inetum-prod"
)

Connector configuration

Each AWS connector instance is configured with:

  • tenant_id — the SecurityV0 tenant (e.g., inetum-prod)
  • aws_account_ids: [str] — list of AWS account IDs this instance scans
  • role_arn_template: strarn:aws:iam::{account_id}:role/SecurityV0ReadOnlyRole
  • external_id: str — per-tenant ExternalId for AssumeRole

The connector iterates aws_account_ids, assumes the role in each account, and stamps all emitted entities with source_system=aws_{service}:{account_id}.

API filtering

The API supports prefix filtering on source_system:

  • GET /entities?source_system=aws_lambda — all Lambda functions across all accounts
  • GET /entities?source_system=aws_lambda:123456789012 — Lambda functions in a specific account
  • GET /entities?source_system_prefix=aws_ — all AWS entities

This requires the source_system index to support prefix queries. If the current MongoDB index does not support this, the platform team must add a source_system_prefix field or update the index strategy.

Cross-account path detection

Cross-account authority paths are detected when a path traverses entities with different account_id components in their source_system fields:

workload.source_system = "aws_lambda:111111111111"
identity.source_system = "aws_iam:222222222222" ← different account

This triggers auth_chain_depth + 1 and may trigger the cross_account_access finding if the destination account has higher sensitivity than the source account.

UI display

The source badge in the authority path detail page renders:

  • aws_lambda:123456789012 → badge: AWS Lambda · 123456789012 (or last 4 digits for brevity)
  • Tooltip: full account ID + account alias (if available via organizations:DescribeAccount)

Consequences

Positive:

  • Entity uniqueness is guaranteed across accounts and customers — no collisions at deduplication
  • Scan scheduling can target individual accounts for rescan without full connector restart
  • Cross-account path detection is structural — no heuristic needed
  • Consistent with how ARNs already encode account scope; source_system just makes it explicit at the entity level

Negative / Trade-offs:

  • source_system values for AWS are longer and less human-readable than flat strings
  • API consumers that filter by exact source_system match (e.g., dashboards hardcoding source_system=aws_lambda) will break; they must use prefix filtering or update to the account-scoped form
  • The MongoDB index on source_system must support prefix queries — verify before Phase 1 ships
  • If a customer reorganises their AWS accounts (splits, merges), source_system values change and path lineage (path_lineage_id) must be migrated