Attacking chain via NHI: Why the Standard IAM Doesn't Close Machine Identifiers
Standard IAM is built around human identity lifecycle: the HR system creates a record, IGA departments accounts, the manager conducts access review, when firing, accounts are deactivated. For NHI, this cycle breaks at each stage. Not because the tools are bad – because NHI is not tied to organizational events.
Service account is created by the developer through gcloud iam service-accounts create, not an HR system. The owner is the author of pull request, who resigned two years ago. Access review is not carried out because no one knows what workload uses this SA and what will break when you change. At the end of the project, the account remains - no one will risk disconnecting. According to Entro Security, the NHI population grew by 44% between 2024 and 2025, and the NHI ratio to human sustainability in cloud-native environments reaches 144:1. One hundred and forty-four machine accounts per man.
Figures for incidents confirm the scale: according to IBM 2024, leaks involving compromised credentials cost an average of $ 4.81 million. According to the Verizon DBIR 2024, stolen accounts are used in the break 80% dataes. Leaks involving compromised credentials (including NHI) are detected and localized in an average of 292 days - almost 10 months, during which the attacker manages to build a full position-nation infrastructure. Recall Capital One: SSRF via misconfigured WAF -> credentials overprivileged IAM-role EC2 through Instance Metadata Service -> access to data of more than 100 million customers. One service account with unnecessary rights.
What Killing File Looks Like Through Compromised NHI
The attacker, which has access to the compromised NHI, acts on a specific chain that is amps on MITRE ATT&CK:
Initial Access - Cloud Accounts (T1078.004) A leaked long-lived API key from a public repository, CI/CD-loga or a randomized in a Docker image is an input point. Nothing needs to be broken: the key is legitimate.
Credential Access - access to Cloud Instation Metadata API (T1552.005) to obtain temporary credentials tied IAM-role. At the same time - Attempt to access Cloud Secrets Management Stores (T1555.006): if the compromised account has secretmanager.versions.access in GCP or secretsmanager:GetSecretValue AWS, Vault and Secrets Manager become a source of additional credentials. Steal Application Access Token (T1528) - interception of OAuth-tokenes from cache or environment variables - expands the set of available identifiers.
Persistence and Privilege Escalation - adding new credentials to existing SA: Other Cloud Credentials (T1098.001) Or the purpose of additional roles: Other Cloud Roles (T1098.003) The attacker creates spare inputs that will survive the rotation of the original key. That’s why one rotation is not enough.
Lateral Movement - with the stolen Application Access Token (T1550.001) the attacker moves between the services. OAuth-token of one microservice opens access to the API of the neighboring proximity if issued with a stock. And it is almost always issued with a reserve.
Defense Evasion - modification of tenant-politics: Domain or Tenant Policy Modification (T1484) - disable audit logging or weakening conditional access rules.
At each stage, the attacker acts on behalf of the legitimate NHI. There is no abnormal user input, there is no atypical IP - only machine traffic indistinguishable from normal. Behavioral baseline for NHI is determined through NIST CSF v2.0 DE.AE-01 (Adverse Event Analysis), but most organizations do not build such a baseline for machine identities. And that's the root of the problem.
Provisioning service accounts and policy-as-code: NHI governance from the first line of code
IaC-first: Terraform modules with mandatory metadata
The first rule is that no production NHI is created manually through the cloud console. Each service account, each IAM role, each OAuth client is described in Terraform or Pulubi and passes code review. This is a enforcement point, without which the audit trail on the provisioning does not exist.
Mandatory fields when creating NHI:
• Owner - specific engineer (email), not team alias and not [email protected]
• TTL TTL- expiration date or decommissioning condition ("remove when archiving the repository X")
• Purpose - one line: why this NHI exists
• Environment - prod / staging / val
• Consumer - what workload, pipeline or agent consumes this NHI
Code:
resource "google_service_account" "deploy_sa" {
account_id = "deploy-${var.service_name}"
display_name = "Deploy pipeline – ${var.service_name}"
description = "owner:${var.owner_email} ttl:${var.ttl_date}"
}
resource "google_project_iam_member" "deploy_binding" {
project = var.project_id
role = "roles/clouddeploy.releaser"
member = "serviceAccount:${google_service_account.deploy_sa.email}"
}
Role roles/clouddeploy.releaser Instead of roles/editor - minimum set of permissions. In the GCP I recommend to include Organization Policy iam.disableServiceAccountKeyCreation, so that JSON keys to SA are not created at all. Instead of keys - Workload Identity Federation: the CI / CD system is authenticated through an OIDC provider, the token lives an hour, nothing is stored on the disk.
Similar cloud patterns: in AWS - IAM Role + OIDC provider instead of IAM User with Access Key. IRSA or EKS Pod Identity (recommended from 2023) for EKS. In Azure - Managed Identity instead of Service Principal with client secret, Workload Identity Federation for AKS. DISA recommends projected service account tokens instead of static secrets in volumes and Pod Security Standards to limit the workloads privileges.
Policy-as-code gate: OPA and Sentinel in CI/CD
IaC is a necessary but insufficient condition. The code is written by people, and roles/owner It is assigned "to accurately work and not waste time on the selection of minimum resolutions." Familiarly? Policy-as-code acts as an automatic guardrail: OPA/Gatekeeper in Kubernetes, HashiCorp Sentinel in Terraform Enterprise, or OPA Conftest for pre-committing Terraform plan.
Code:
deny[msg] {
rc := input.resource_changes[_]
rc.type == "google_service_account"
desc := object.get(rc.change.after, "description", "")
not contains(desc, "owner:")
msg := "SA must include owner: in description"
}
deny[msg] {
rc := input.resource_changes[_]
rc.type == "google_project_iam_member"
endswith(rc.change.after.role, "/owner")
msg := sprintf("Primitive role %v blocked for NHI", [rc.change.after.role])
}
Pipeline: terraform plan -out=plan.json -> terraform show -json plan.json -> conftest test plan.json. In case of violation, PR is blocked. Override - only through two approval of security engineers with a record in the audit log.
A policy kit for production NHI, which closes typical errors:
• Prohibition of Primary Roles (roles/owner, roles/editor, roles/viewer) for service accounts
• Mandatory labels: owner, ttl, env, consumer-workload
• Limitation of IAM-bindings by one SA (no more than 3 roles; if you need more - to revise the architecture)
• Prohibition of Binding to allUsersor allAuthenticatedUsers
• Prohibition of resource google_service_account_key- if the key is needed, a separate exusement workflow is used
This approach is implementing control NIST SP 800-53 AC-1 (Access Control Policy) and IA-1 (Identification and Authentication Policy): policies are documented in code, applied automatically, audited and versioned via git history. No "politicians at Confluence, which no one has read."
Automation of secrets rotation: from Vault to Workload Identity Federation
The choice depends on the infrastructure: if everything is in one cloud - cloud-native secret manager is easier and cheaper. Multi-cloud or hybrid - Vault as a single control plane. Only Kubernetes - cert-manager + SPIRE cover the mTLS layer, but for API keys to SaaS services will still need a Vault or cloud-native store.
CIEM non-human identities: Detection and elimination of excessive privileges
CIEM (Cloud Infrastructure Entitlement Management) closes the gap between “what permits issued by NHI” and “what NHI really uses”. Privilege creep in machine accounts is more aggressive than that of users: access review is less common, there is often no responsible owner, and when creating a permit, they are issued "with a reserve".
A characteristic picture that I see over and over again: service account for CI/CD-delose roles/editor when created, and for 18 months out of several thousand accessible permissions, 12 used. Twelve. The remaining thousands are a clean attack surface. CIEM solutions (Wiz, Tenable Cloud / former Ermetic, Prisma Cloud, CrowdStrike Falcon Cloud Security) analyze CloudTrill, GCP Cloud Audit Logs, Azure Activity Logs and build an per-identity plan effective vs actual usage, sorted by risk score.
From CIEM-Alerta to right-sizing: a practical script
1. CIEM detects: SA deploy-pipeline-paymentshas roles/editor, in 90 days used 12 specific permissions
2. CIEM generates recommendation: custom role with a list of these 12 seats
3. DevSecOps Engineer Creates PR with custom definition role in Terraform
4. OPA-politics validizes: custom role does not contain wildcardread, labels on site
5. terraform applyswitches SA to the role custom
6. Monitoring 7 days through Cloud Audit Logs: if workload does not generate 403 Forbidden- old roles/editorbinding is removed
7. Entry in access certification log: SA right-sized, date, engineer, justification
This process is the continuous access certification nucleus for NHI. It corresponds to NIST CSF v2.0 PR.AA-01 (identities and credentials management of authorized services) and ID.AM-01 (inventory and asset management).
The scale in which manual analysis is not possible: the audit of one Fortune 500 financial organization found more than 4.2 million non-human identities with ~50 000 human accounts, according to CSA data. With such volumes, CIEM is not convenience, but a necessity. For the company with 200-500 NHI in one cloud - you can start with a self-written script that parsite CloudTrail and compares the GDP vs used permission. At thousands of NHI in multi-cloud - without a CIEM-product can not do.
Review of the Privileges of Machine Identifiers: Brownout Protocol and Emergency Revoke
Decommissioning is a phase where most NHI programs die. The reason is banal: the fear of breaking the sale. “This SA was not used for 6 months, but suddenly it is needed for annual reporting?” and the account remains. According to the data OWASP Non-Human Identities Top 10, improper offboarding is consistently included in critical risks.
The Brownout protocol solves this dilemma through a controlled shutdown instead of divination on the coffee grounds:
1. Non-activity Detection : NHI has not been authenticated for more than 30 days (prod) or 14 days (dev/staging)
2. Disable: The account is disconnected but not deleted. gcloud iam service-accounts disablein the GCP, aws iam update-access-key --status Inactivein AWS. Credentials are disabled, but the object itself is preserved
3. Surveillance (24-48 hours): monitoring of failed authentication from workloads that try to use the disabled NHI
4. Solution : No attempts to authenticate during the observation period are permanent delete. There were attempts - the account is restored, but immediately assigned a owner for access review with a deadline
For emergency review, when confirmed compromising brownout, we do not apply - you need an immediate revoke. Automated playbook:
• Review of all keys: gcloud iam service-accounts keys list+ deletefor everyone
• Review of all Vault leases for dynamic secrets: vault lease revoke -prefix database/creds/<role>/(for KV v2 secrets leases are not created - recall through rotation of the value or vault kv metadata delete)
• Review of OAuth tokens via provider API
• Switch on standby workload identity, if the architecture provides hot standby
• Notification in security channel with timeline and blast radius assessment
According to NIST SP 800-53 IR-1, response procedures should be documented and tested before the incident. Playbook emergency revocation of NHI - part of the incident response, not improvisation during the fire. If you train tabletop without a script "compromised service account" - add.
Checklist: life cycle management non-human identities
The numbered list for inclusion in the audit report or transfer to the DevOps/Platform team:
1. Start discovery: list all NHI in the cloud IAM, Active Directory, Kubernetes, CI/CD. For each, fix the owner, consumer workload, date of creation, date of the last authentication
2. Insert Organization Policy iam.disableServiceAccountKeyCreation(GCP) or SCP with pron iam:CreateAccessKeyfor all but break-glass role (AWS)
3. All new NHIs create only through Terraform/Pulumi modules with mandatory labels: owner, ttl, env, consumer
4. Deploy OPA Conftest or Sentinel as mandatory CI/CD check. Minimum policy kit: Prominive role ban, mandatory metadata, wildcard permissions, banning SA creation key
5. Configure automatic rotation long-lived credentials with a period of no more than 90 days. New workloads - only dynamic or short-lived credentials (Vault Dynamic Secrets, Workload Identity Federation)
6. Connect CIEM to CloudTrail / Audit Logs. Configure the weekly report by NHI with gap more than 50% between the board and permission useds
7. Automatic disable NHI inactive more than 30 days (prod) / 14 days (dev/staging) with brownout protocol
8. Login of all operations with NHI credentials: creation, rotation, use, review. Storage for at least 1 year (NIST SP 800-53 AU-1)
9. Quarterly access certification for all NHI with privileges above read-only. Each NHI is presented to the assigned owner for re-certification
10. Document and test the NHI credentials emergency review playbook: from compromising detection to full revoke - target time less than 15 minutes
Standard IAM is built around human identity lifecycle: the HR system creates a record, IGA departments accounts, the manager conducts access review, when firing, accounts are deactivated. For NHI, this cycle breaks at each stage. Not because the tools are bad – because NHI is not tied to organizational events.
Service account is created by the developer through gcloud iam service-accounts create, not an HR system. The owner is the author of pull request, who resigned two years ago. Access review is not carried out because no one knows what workload uses this SA and what will break when you change. At the end of the project, the account remains - no one will risk disconnecting. According to Entro Security, the NHI population grew by 44% between 2024 and 2025, and the NHI ratio to human sustainability in cloud-native environments reaches 144:1. One hundred and forty-four machine accounts per man.
Figures for incidents confirm the scale: according to IBM 2024, leaks involving compromised credentials cost an average of $ 4.81 million. According to the Verizon DBIR 2024, stolen accounts are used in the break 80% dataes. Leaks involving compromised credentials (including NHI) are detected and localized in an average of 292 days - almost 10 months, during which the attacker manages to build a full position-nation infrastructure. Recall Capital One: SSRF via misconfigured WAF -> credentials overprivileged IAM-role EC2 through Instance Metadata Service -> access to data of more than 100 million customers. One service account with unnecessary rights.
What Killing File Looks Like Through Compromised NHI
The attacker, which has access to the compromised NHI, acts on a specific chain that is amps on MITRE ATT&CK:
Initial Access - Cloud Accounts (T1078.004) A leaked long-lived API key from a public repository, CI/CD-loga or a randomized in a Docker image is an input point. Nothing needs to be broken: the key is legitimate.
Credential Access - access to Cloud Instation Metadata API (T1552.005) to obtain temporary credentials tied IAM-role. At the same time - Attempt to access Cloud Secrets Management Stores (T1555.006): if the compromised account has secretmanager.versions.access in GCP or secretsmanager:GetSecretValue AWS, Vault and Secrets Manager become a source of additional credentials. Steal Application Access Token (T1528) - interception of OAuth-tokenes from cache or environment variables - expands the set of available identifiers.
Persistence and Privilege Escalation - adding new credentials to existing SA: Other Cloud Credentials (T1098.001) Or the purpose of additional roles: Other Cloud Roles (T1098.003) The attacker creates spare inputs that will survive the rotation of the original key. That’s why one rotation is not enough.
Lateral Movement - with the stolen Application Access Token (T1550.001) the attacker moves between the services. OAuth-token of one microservice opens access to the API of the neighboring proximity if issued with a stock. And it is almost always issued with a reserve.
Defense Evasion - modification of tenant-politics: Domain or Tenant Policy Modification (T1484) - disable audit logging or weakening conditional access rules.
At each stage, the attacker acts on behalf of the legitimate NHI. There is no abnormal user input, there is no atypical IP - only machine traffic indistinguishable from normal. Behavioral baseline for NHI is determined through NIST CSF v2.0 DE.AE-01 (Adverse Event Analysis), but most organizations do not build such a baseline for machine identities. And that's the root of the problem.
Provisioning service accounts and policy-as-code: NHI governance from the first line of code
IaC-first: Terraform modules with mandatory metadata
The first rule is that no production NHI is created manually through the cloud console. Each service account, each IAM role, each OAuth client is described in Terraform or Pulubi and passes code review. This is a enforcement point, without which the audit trail on the provisioning does not exist.
Mandatory fields when creating NHI:
• Owner - specific engineer (email), not team alias and not [email protected]
• TTL TTL- expiration date or decommissioning condition ("remove when archiving the repository X")
• Purpose - one line: why this NHI exists
• Environment - prod / staging / val
• Consumer - what workload, pipeline or agent consumes this NHI
Code:
resource "google_service_account" "deploy_sa" {
account_id = "deploy-${var.service_name}"
display_name = "Deploy pipeline – ${var.service_name}"
description = "owner:${var.owner_email} ttl:${var.ttl_date}"
}
resource "google_project_iam_member" "deploy_binding" {
project = var.project_id
role = "roles/clouddeploy.releaser"
member = "serviceAccount:${google_service_account.deploy_sa.email}"
}
Role roles/clouddeploy.releaser Instead of roles/editor - minimum set of permissions. In the GCP I recommend to include Organization Policy iam.disableServiceAccountKeyCreation, so that JSON keys to SA are not created at all. Instead of keys - Workload Identity Federation: the CI / CD system is authenticated through an OIDC provider, the token lives an hour, nothing is stored on the disk.
Similar cloud patterns: in AWS - IAM Role + OIDC provider instead of IAM User with Access Key. IRSA or EKS Pod Identity (recommended from 2023) for EKS. In Azure - Managed Identity instead of Service Principal with client secret, Workload Identity Federation for AKS. DISA recommends projected service account tokens instead of static secrets in volumes and Pod Security Standards to limit the workloads privileges.
Policy-as-code gate: OPA and Sentinel in CI/CD
IaC is a necessary but insufficient condition. The code is written by people, and roles/owner It is assigned "to accurately work and not waste time on the selection of minimum resolutions." Familiarly? Policy-as-code acts as an automatic guardrail: OPA/Gatekeeper in Kubernetes, HashiCorp Sentinel in Terraform Enterprise, or OPA Conftest for pre-committing Terraform plan.
Code:
deny[msg] {
rc := input.resource_changes[_]
rc.type == "google_service_account"
desc := object.get(rc.change.after, "description", "")
not contains(desc, "owner:")
msg := "SA must include owner: in description"
}
deny[msg] {
rc := input.resource_changes[_]
rc.type == "google_project_iam_member"
endswith(rc.change.after.role, "/owner")
msg := sprintf("Primitive role %v blocked for NHI", [rc.change.after.role])
}
Pipeline: terraform plan -out=plan.json -> terraform show -json plan.json -> conftest test plan.json. In case of violation, PR is blocked. Override - only through two approval of security engineers with a record in the audit log.
A policy kit for production NHI, which closes typical errors:
• Prohibition of Primary Roles (roles/owner, roles/editor, roles/viewer) for service accounts
• Mandatory labels: owner, ttl, env, consumer-workload
• Limitation of IAM-bindings by one SA (no more than 3 roles; if you need more - to revise the architecture)
• Prohibition of Binding to allUsersor allAuthenticatedUsers
• Prohibition of resource google_service_account_key- if the key is needed, a separate exusement workflow is used
This approach is implementing control NIST SP 800-53 AC-1 (Access Control Policy) and IA-1 (Identification and Authentication Policy): policies are documented in code, applied automatically, audited and versioned via git history. No "politicians at Confluence, which no one has read."
Automation of secrets rotation: from Vault to Workload Identity Federation
The choice depends on the infrastructure: if everything is in one cloud - cloud-native secret manager is easier and cheaper. Multi-cloud or hybrid - Vault as a single control plane. Only Kubernetes - cert-manager + SPIRE cover the mTLS layer, but for API keys to SaaS services will still need a Vault or cloud-native store.
CIEM non-human identities: Detection and elimination of excessive privileges
CIEM (Cloud Infrastructure Entitlement Management) closes the gap between “what permits issued by NHI” and “what NHI really uses”. Privilege creep in machine accounts is more aggressive than that of users: access review is less common, there is often no responsible owner, and when creating a permit, they are issued "with a reserve".
A characteristic picture that I see over and over again: service account for CI/CD-delose roles/editor when created, and for 18 months out of several thousand accessible permissions, 12 used. Twelve. The remaining thousands are a clean attack surface. CIEM solutions (Wiz, Tenable Cloud / former Ermetic, Prisma Cloud, CrowdStrike Falcon Cloud Security) analyze CloudTrill, GCP Cloud Audit Logs, Azure Activity Logs and build an per-identity plan effective vs actual usage, sorted by risk score.
From CIEM-Alerta to right-sizing: a practical script
1. CIEM detects: SA deploy-pipeline-paymentshas roles/editor, in 90 days used 12 specific permissions
2. CIEM generates recommendation: custom role with a list of these 12 seats
3. DevSecOps Engineer Creates PR with custom definition role in Terraform
4. OPA-politics validizes: custom role does not contain wildcardread, labels on site
5. terraform applyswitches SA to the role custom
6. Monitoring 7 days through Cloud Audit Logs: if workload does not generate 403 Forbidden- old roles/editorbinding is removed
7. Entry in access certification log: SA right-sized, date, engineer, justification
This process is the continuous access certification nucleus for NHI. It corresponds to NIST CSF v2.0 PR.AA-01 (identities and credentials management of authorized services) and ID.AM-01 (inventory and asset management).
The scale in which manual analysis is not possible: the audit of one Fortune 500 financial organization found more than 4.2 million non-human identities with ~50 000 human accounts, according to CSA data. With such volumes, CIEM is not convenience, but a necessity. For the company with 200-500 NHI in one cloud - you can start with a self-written script that parsite CloudTrail and compares the GDP vs used permission. At thousands of NHI in multi-cloud - without a CIEM-product can not do.
Review of the Privileges of Machine Identifiers: Brownout Protocol and Emergency Revoke
Decommissioning is a phase where most NHI programs die. The reason is banal: the fear of breaking the sale. “This SA was not used for 6 months, but suddenly it is needed for annual reporting?” and the account remains. According to the data OWASP Non-Human Identities Top 10, improper offboarding is consistently included in critical risks.
The Brownout protocol solves this dilemma through a controlled shutdown instead of divination on the coffee grounds:
1. Non-activity Detection : NHI has not been authenticated for more than 30 days (prod) or 14 days (dev/staging)
2. Disable: The account is disconnected but not deleted. gcloud iam service-accounts disablein the GCP, aws iam update-access-key --status Inactivein AWS. Credentials are disabled, but the object itself is preserved
3. Surveillance (24-48 hours): monitoring of failed authentication from workloads that try to use the disabled NHI
4. Solution : No attempts to authenticate during the observation period are permanent delete. There were attempts - the account is restored, but immediately assigned a owner for access review with a deadline
For emergency review, when confirmed compromising brownout, we do not apply - you need an immediate revoke. Automated playbook:
• Review of all keys: gcloud iam service-accounts keys list+ deletefor everyone
• Review of all Vault leases for dynamic secrets: vault lease revoke -prefix database/creds/<role>/(for KV v2 secrets leases are not created - recall through rotation of the value or vault kv metadata delete)
• Review of OAuth tokens via provider API
• Switch on standby workload identity, if the architecture provides hot standby
• Notification in security channel with timeline and blast radius assessment
According to NIST SP 800-53 IR-1, response procedures should be documented and tested before the incident. Playbook emergency revocation of NHI - part of the incident response, not improvisation during the fire. If you train tabletop without a script "compromised service account" - add.
Checklist: life cycle management non-human identities
The numbered list for inclusion in the audit report or transfer to the DevOps/Platform team:
1. Start discovery: list all NHI in the cloud IAM, Active Directory, Kubernetes, CI/CD. For each, fix the owner, consumer workload, date of creation, date of the last authentication
2. Insert Organization Policy iam.disableServiceAccountKeyCreation(GCP) or SCP with pron iam:CreateAccessKeyfor all but break-glass role (AWS)
3. All new NHIs create only through Terraform/Pulumi modules with mandatory labels: owner, ttl, env, consumer
4. Deploy OPA Conftest or Sentinel as mandatory CI/CD check. Minimum policy kit: Prominive role ban, mandatory metadata, wildcard permissions, banning SA creation key
5. Configure automatic rotation long-lived credentials with a period of no more than 90 days. New workloads - only dynamic or short-lived credentials (Vault Dynamic Secrets, Workload Identity Federation)
6. Connect CIEM to CloudTrail / Audit Logs. Configure the weekly report by NHI with gap more than 50% between the board and permission useds
7. Automatic disable NHI inactive more than 30 days (prod) / 14 days (dev/staging) with brownout protocol
8. Login of all operations with NHI credentials: creation, rotation, use, review. Storage for at least 1 year (NIST SP 800-53 AU-1)
9. Quarterly access certification for all NHI with privileges above read-only. Each NHI is presented to the assigned owner for re-certification
10. Document and test the NHI credentials emergency review playbook: from compromising detection to full revoke - target time less than 15 minutes