This check flags any GCP identity that holds both an admin-level role and a KMS CryptoKey role. That combination lets one principal manage resources and the keys protecting them, which breaks separation of duties. Split the permissions across different identities so no single account can both encrypt or decrypt data and administer the systems that use it.
Key management is one of the few places in cloud IAM where the principle of least privilege is non-negotiable. The whole point of a Key Management Service is to draw a line between the people who run your infrastructure and the keys that protect your data. When a single user or service account sits on both sides of that line, the line stops meaning anything.
The Lensix check iam_adminandkmsroles looks for exactly this overlap in your GCP projects: an identity that has been granted broad administrative power and the ability to use or manage Cloud KMS CryptoKeys at the same time.
What this check detects
The check inspects IAM policy bindings across your project and surfaces any member (a user, group, or service account) that holds both of the following:
- An admin-level role, such as
roles/owner,roles/editor, a service-specific admin likeroles/compute.admin, orroles/iam.securityAdmin. - A KMS CryptoKey role, such as
roles/cloudkms.admin,roles/cloudkms.cryptoKeyEncrypterDecrypter,roles/cloudkms.cryptoKeyEncrypter, orroles/cloudkms.cryptoKeyDecrypter.
On their own, neither role is a problem. The risk comes from the concentration. An identity with admin rights can change, move, or destroy resources. The same identity holding CryptoKey rights can also unwrap the data those resources depend on. That is enough to read protected data, swap out keys, or quietly disable an audit trail.
Note: Cloud KMS roles come in two flavors. Management roles like roles/cloudkms.admin let a principal create, rotate, and destroy keys. Usage roles like roles/cloudkms.cryptoKeyEncrypterDecrypter let a principal actually encrypt and decrypt data with a key. Either one paired with admin access trips this check, because both undermine separation of duties in different ways.
Why it matters
Separation of duties exists so that compromising one account does not hand an attacker the whole kingdom. KMS is the clearest example. If your data is encrypted with customer-managed encryption keys (CMEK), then the key is the last control standing between an attacker and your plaintext. An admin who also controls that key removes the last control.
Attack scenario: the over-privileged service account
Say you have a CI/CD service account that deploys to Compute Engine. Someone granted it roles/editor to keep things simple, then later added roles/cloudkms.cryptoKeyEncrypterDecrypter so it could decrypt a config file at deploy time. A leaked key for that service account now means an attacker can:
- Spin up or modify compute resources anywhere in the project.
- Decrypt any data protected by the keys that account can reach.
- Exfiltrate plaintext without ever touching the storage layer directly.
Because the same identity is doing both legitimately, the activity blends into normal operations. There is no second account to cross-check against, which makes detection harder and incident response slower.
The insider and the audit problem
With roles/cloudkms.admin plus a project admin role, a single insider can destroy a key version and the resources that referenced it in one sitting. They can also alter logging configuration if their admin role includes that scope. Auditors hate this for a reason: you can no longer prove that no single person had the means to both access and erase sensitive data.
Warning: Compliance frameworks including PCI DSS, SOC 2, and ISO 27001 explicitly call for separation of duties around encryption key management. A finding from this check is the kind of thing that turns into an audit exception if left unresolved.
How to fix it
The fix is to split the overlapping permissions across different identities. Decide which role the flagged identity actually needs, then remove the other and reassign it to a dedicated principal.
Step 1: Find the offending bindings
List the IAM policy and search for the member to confirm both roles are present.
gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.members:user:[email protected]" \
--format="table(bindings.role)"
This returns every role assigned to that member. Look for an admin role and a cloudkms role appearing together.
Step 2: Decide who owns what
Pick one of two clean separations:
- Keep admin, drop KMS: if this is an infrastructure operator who should not touch encryption, remove the CryptoKey role.
- Keep KMS, drop admin: if this is a workload that only needs to encrypt and decrypt, strip the broad admin role and grant the narrowest KMS usage role instead.
Danger: Before removing a KMS role from a service account, confirm nothing in production depends on it. If a running workload uses that account to decrypt data, pulling the binding will cause decryption failures and outages. Check Cloud Audit Logs for recent cloudkms.cryptoKeyVersions.useToDecrypt activity first.
Step 3: Remove the overlapping role
To remove the KMS usage role from an admin identity:
gcloud projects remove-iam-policy-binding PROJECT_ID \
--member="user:[email protected]" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter"
Or remove the broad admin role from a workload identity:
gcloud projects remove-iam-policy-binding PROJECT_ID \
--member="serviceAccount:deploy@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/editor"
Step 4: Grant the replacement at the right scope
For workloads, bind the KMS role at the key level, not the project level. That limits the blast radius to a single key.
gcloud kms keys add-iam-policy-binding KEY_NAME \
--keyring=KEYRING_NAME \
--location=LOCATION \
--member="serviceAccount:deploy@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyDecrypter"
If the workload only ever decrypts, grant cryptoKeyDecrypter rather than cryptoKeyEncrypterDecrypter. Match the role to the actual operation.
Tip: Grant KMS roles on the specific CryptoKey or KeyRing instead of the project. A project-level cloudkms.cryptoKeyEncrypterDecrypter binding gives access to every key in the project, which is almost never what you want.
Fixing it in Terraform
If you manage IAM as code, the cleanest pattern is to bind KMS roles at the key resource and keep admin roles separate. Here is a setup that keeps the two responsibilities on different identities:
resource "google_project_iam_member" "infra_admin" {
project = var.project_id
role = "roles/compute.admin"
member = "user:[email protected]"
}
resource "google_kms_crypto_key_iam_member" "workload_decrypt" {
crypto_key_id = google_kms_crypto_key.data_key.id
role = "roles/cloudkms.cryptoKeyDecrypter"
member = "serviceAccount:deploy@${var.project_id}.iam.gserviceaccount.com"
}
Notice that the admin role and the KMS role go to two different members. Terraform makes this drift visible: if someone adds an overlapping binding by hand, a terraform plan will flag it.
How to prevent it from happening again
Manual cleanup only helps until the next person grants a convenient roles/editor. Push the guardrail upstream.
Policy as code in CI
Add a check to your pipeline that fails when a single member has both an admin role and a KMS role. With Open Policy Agent and Conftest, a rule against your Terraform plan looks like this:
package gcp.iam
admin_roles := {"roles/owner", "roles/editor", "roles/compute.admin"}
kms_roles := {"roles/cloudkms.admin", "roles/cloudkms.cryptoKeyEncrypterDecrypter"}
deny[msg] {
some member
member_roles := {r | r := input.bindings[_].members[member]; r != ""}
count(member_roles & admin_roles) > 0
count(member_roles & kms_roles) > 0
msg := sprintf("member %v holds both admin and KMS roles", [member])
}
Organization policy and custom roles
Use IAM custom roles to give people exactly the permissions they need instead of reaching for roles/editor. The broad primitive roles are the most common source of these findings because they quietly include permissions people forget about.
Tip: Lensix runs iam_adminandkmsroles continuously, so a risky binding that slips past CI gets surfaced within a scan cycle rather than waiting for your next audit. Wire the finding into your alerting so it lands in the right channel automatically.
Best practices
- Separate the key admin from the key user. One identity rotates and manages keys, a different one uses them. Neither should have project admin.
- Bind KMS roles at the key or keyring level. Project-wide KMS grants defeat the purpose of having per-data keys.
- Prefer the narrowest role. Use
cryptoKeyDecrypterorcryptoKeyEncrypterover the combined role when a workload only does one operation. - Avoid primitive roles.
roles/ownerandroles/editorsweep in permissions you did not intend to grant. Replace them with predefined or custom roles. - Audit regularly. Review KMS bindings with Cloud Audit Logs and reconcile them against what each identity actually needs.
- Keep key destruction behind a second identity. The person who can run your infrastructure should not be able to single-handedly delete the keys protecting it.
Encryption only protects you when the people who manage your systems cannot also reach into the keys at will. Splitting these roles costs a few minutes of IAM cleanup and buys you a real boundary that holds up under both a breach and an audit.

