aws-ssm-params-cross-account-read
A consumer reads a RAM-shared SecureString whose creds grant the owning account.
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 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) |
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}.'