aws-ssm-params-cross-account-read

A consumer reads a RAM-shared SecureString whose creds grant the owning account.

derived aws emits CanEnterAccount

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'consumer': None} CanReadSecret {'param': None} {'param': None} CredentialsFor {'target': None} {'share': None} CrossAccountTrust {'consumerAccount': None}

where

node_class(?param) == Secret ?param.origin_service == aws:ssm-params ?param.tier == Advanced ?consumer.account != ?param.account ?share is the resource policy / RAM share OF ?param (the share grants ?consumer read on ?param) ?consumerAccount == ?consumer.account # the trust names the consumer's account ?target resolves to an identity/resource in ?param.account (CredentialsFor target lives in the owning account) ?owningAccount is the Account boundary node that Contains ?param (i.e. the node whose id == ?param.account); bind it as the emit target

emit

source typeIdentity
target typeAccount
source?consumer
target?owningAccount
permissionsssm:GetParameter kms:Decrypt
conditionsresource_policy key_permission
state logicif the CrossAccountTrust (RAM/resource-policy) grant is confirmed AND (SecureString CMK is shared to ?consumer via the KMS key policy AND ?consumer has kms:Decrypt): ACTIVE; else CONDITIONAL(resource_policy, key_permission)

Narrative

{consumer.name} (account {consumer.account}) can read the shared SecureString {param.name} and use its credentials to gain a foothold in account {param.account}.

Raw rule rules/derived/aws/ssm-params.yaml

id: aws-ssm-params-cross-account-read
emits: CanEnterAccount
description: A consumer reads a RAM-shared SecureString whose creds grant the owning account.
match:
- - consumer: null
  - CanReadSecret
  - param: null
- - param: null
  - CredentialsFor
  - target: null
- - share: null
  - CrossAccountTrust
  - consumerAccount: null
where:
- node_class(?param) == Secret
- ?param.origin_service == aws:ssm-params
- ?param.tier == Advanced
- ?consumer.account != ?param.account
- ?share is the resource policy / RAM share OF ?param (the share grants ?consumer read on ?param)
- '?consumerAccount == ?consumer.account  # the trust names the consumer''s account'
- ?target resolves to an identity/resource in ?param.account (CredentialsFor target lives in the owning
  account)
- ?owningAccount is the Account boundary node that Contains ?param (i.e. the node whose id == ?param.account);
  bind it as the emit target
emit:
  source_type: Identity
  target_type: Account
  source: ?consumer
  target: ?owningAccount
  permissions:
  - ssm:GetParameter
  - kms:Decrypt
  conditions:
  - resource_policy
  - key_permission
  state_logic: 'if the CrossAccountTrust (RAM/resource-policy) grant is confirmed AND (SecureString CMK
    is shared to ?consumer via the KMS key policy AND ?consumer has kms:Decrypt): ACTIVE; else CONDITIONAL(resource_policy,
    key_permission)'
  confidence: min(contributing_confidences) * 0.85
  derived_from:
  - <CanReadSecret edge_id>
  - <CredentialsFor edge_id>
  - <CrossAccountTrust (aws-ssm-params-cross-account-share) edge_id>
  false_positive_note: "Cross-account read is ADVANCED-tier only and READ-ONLY. Critically, the default\
    \ AWS-managed alias/aws/ssm key CANNOT be shared cross-account \u2014 a shareable SecureString MUST\
    \ use a customer-managed CMK that is ALSO shared via the KMS key policy AND the consumer must hold\
    \ kms:Decrypt on it; until confirmed, emit CONDITIONAL(key_permission). Requires the resolved credential\
    \ to actually authenticate in the owning account (CredentialsFor -> owning-account identity), not\
    \ merely be read."
  narrative: '{consumer.name} (account {consumer.account}) can read the shared SecureString {param.name}
    and use its credentials to gain a foothold in account {param.account}.'
move · open · esc close