aws-kms-cross-account-credential-entry
Cross-account decrypt that unlocks THIS account's credentials is an account foothold.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'external': None}
CanDecrypt {'key': None}
{'acct': None} Contains {'key': None}
{'secret': None} ContainsResourceReference {'key': None}
{'secret': None} CredentialsFor {'identity': None}
where
node_class(?external) == Identity (
CanEnterAccount source is [Identity])
node_class(?acct) == Account (Contains source is [AdministrativeBoundary]; binds the real Account node that owns ?key so the CanEnterAccount target is a bound Account, not a placeholder string)
structural 'encrypted-by' link: ContainsResourceReference(secret->key), NOT LocatedIn (whose target set is [AdministrativeBoundary, Network]); see the source-class caveat in aws-kms-decrypt-unlocks-secret
?external.account != ?acct.account
?identity is a principal in ?acct (the ciphertext is a credential for THIS account)
?external can also read the ciphertext store (the secret/blob), not just the key
emit
| source type | Identity |
|---|---|
| target type | Account |
| source | ?external |
| target | ?acct |
| permissions | kms:Decrypt |
| conditions | resource_policy iam_permission condition_expression |
| state logic | if ?external can both read the ciphertext store AND decrypt it, and the plaintext is a valid credential for ?acct: ACTIVE; else CONDITIONAL(iam_permission) |
Narrative
{external.name} can decrypt {secret.name} in account {acct.name} and obtain credentials for {identity.name}, gaining a foothold in that account.
Raw rule rules/derived/aws/kms.yaml
id: aws-kms-cross-account-credential-entry
emits: CanEnterAccount
description: Cross-account decrypt that unlocks THIS account's credentials is an account foothold.
match:
- - external: null
- CanDecrypt
- key: null
- - acct: null
- Contains
- key: null
- - secret: null
- ContainsResourceReference
- key: null
- - secret: null
- CredentialsFor
- identity: null
where:
- node_class(?external) == Identity (CanEnterAccount source is [Identity])
- node_class(?acct) == Account (Contains source is [AdministrativeBoundary]; binds the real Account node
that owns ?key so the CanEnterAccount target is a bound Account, not a placeholder string)
- 'structural ''encrypted-by'' link: ContainsResourceReference(secret->key), NOT LocatedIn (whose target
set is [AdministrativeBoundary, Network]); see the source-class caveat in aws-kms-decrypt-unlocks-secret'
- ?external.account != ?acct.account
- ?identity is a principal in ?acct (the ciphertext is a credential for THIS account)
- ?external can also read the ciphertext store (the secret/blob), not just the key
emit:
source_type: Identity
target_type: Account
source: ?external
target: ?acct
permissions:
- kms:Decrypt
conditions:
- resource_policy
- iam_permission
- condition_expression
state_logic: 'if ?external can both read the ciphertext store AND decrypt it, and the plaintext is a
valid credential for ?acct: ACTIVE; else CONDITIONAL(iam_permission)'
confidence: min(contributing_confidences) * 0.8
derived_from:
- <CanDecrypt edge_id>
- <Contains account->key edge_id>
- <ContainsResourceReference secret->key edge_id>
- <CredentialsFor secret->identity edge_id>
false_positive_note: "Bare cross-account kms:Decrypt is NOT account entry \u2014 only when the decrypted\
\ material is itself a usable credential for the key's account AND the external principal can also\
\ read the ciphertext store. Otherwise emit aws-kms-cross-account-key-use (CanDecrypt) instead."
narrative: '{external.name} can decrypt {secret.name} in account {acct.name} and obtain credentials
for {identity.name}, gaining a foothold in that account.'