read-credential-yields-identity

Reading a credential that is credentials for an identity yields the ability to authenticate as that identity.

derived any cloud emits CanImpersonate

match

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

{'principal': None} CanReadCredential {'credential': None} {'credential': None} CredentialsFor {'identity': None}

where

node_class(?identity) == Identity node_type(?identity) in [ServiceAccount, MachineIdentity, ManagedIdentity, WorkloadIdentity, ApplicationIdentity] # Azure SP, GCP SA, k8s SA, AWS service account variants

emit

source typeIdentity
source?principal
target?identity
conditionsiam_permission resource_policy key_permission
state logicinherit_weakest(CanReadCredential, CredentialsFor): if the CanReadCredential input is CONDITIONAL (e.g., key_permission on CMEK-wrapped credential, decrypt not yet proven) then CONDITIONAL carries forward; if either input is BLOCKED then BLOCKED; else ACTIVE. CredentialsFor is a zero-cost link and never downgrades on its own.

Narrative

{principal.name} can read {credential.name} ({perm:CanReadCredential}), which holds credentials for {identity.name}; reading it lets {principal.name} authenticate and act as {identity.name}.

Raw rule rules/derived/credential-chains.yaml

id: read-credential-yields-identity
emits: CanImpersonate
description: Reading a credential that is credentials for an identity yields the ability to authenticate
  as that identity.
applies_to:
- '*'
match:
- - principal: null
  - CanReadCredential
  - credential: null
- - credential: null
  - CredentialsFor
  - identity: null
where:
- node_class(?identity) == Identity
- 'node_type(?identity) in [ServiceAccount, MachineIdentity, ManagedIdentity, WorkloadIdentity, ApplicationIdentity]  #
  Azure SP, GCP SA, k8s SA, AWS service account variants'
emit:
  source_type: Identity
  source: ?principal
  target: ?identity
  permissions: []
  conditions:
  - iam_permission
  - resource_policy
  - key_permission
  state_logic: 'inherit_weakest(CanReadCredential, CredentialsFor): if the CanReadCredential input is
    CONDITIONAL (e.g., key_permission on CMEK-wrapped credential, decrypt not yet proven) then CONDITIONAL
    carries forward; if either input is BLOCKED then BLOCKED; else ACTIVE. CredentialsFor is a zero-cost
    link and never downgrades on its own.'
  confidence: min(contributing_confidences)
  derived_from:
  - ?principal CanReadCredential ?credential
  - ?credential CredentialsFor ?identity
  false_positive_note: "Only emit when the credential genuinely authenticates the target identity (CredentialsFor\
    \ must be an explicit, evidenced link \u2014 e.g. an AD machine account AccessKey, an SSH key, an\
    \ API token, or a signing certificate bound to that principal), not merely a credential the identity\
    \ happens to own or that sits in its namespace. If the credential is rotated/disabled/expired, the\
    \ adapter should not have emitted CredentialsFor -> no impersonation. When the CanReadCredential is\
    \ CONDITIONAL(key_permission), do NOT report this as an ACTIVE impersonation path until key_permission\
    \ gate is satisfied. For a plain User backed by a password, prefer CredentialValidFor/AuthenticatesTo\
    \ \u2014 do not force a CanImpersonate whose target set excludes User."
  narrative: '{principal.name} can read {credential.name} ({perm:CanReadCredential}), which holds credentials
    for {identity.name}; reading it lets {principal.name} authenticate and act as {identity.name}.'
move · open · esc close