Back to Checks
gcp

Project members are not assigned both Cloud KMS Admin and CryptoKey Encrypter/Decrypter roles

iam_role_kms_enforce_separation_of_duties
Service: iamSeverity: medium
OfficialBuilt-inVerified
Categories:identity-accessencryption
Resource Type:cloudresourcemanager.googleapis.com/Project

Project IAM assignments are analyzed for Cloud KMS separation of duties: principals simultaneously granted roles/cloudkms.admin and any of roles/cloudkms.cryptoKeyEncrypterDecrypter, roles/cloudkms.cryptoKeyEncrypter, or roles/cloudkms.cryptoKeyDecrypter.

Risk

Combining key management and key usage undermines confidentiality, integrity, and availability:

  • Unauthorized decryption of sensitive data
  • Tampering with policies or rotation to conceal access
  • Disabling or destroying keys, causing outages

This concentration of power reduces oversight and auditability.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler gcp --checks iam_role_kms_enforce_separation_of_duties

Recommendation

Apply least privilege and separation of duties:

  • Never combine roles/cloudkms.admin with any roles/cloudkms.cryptoKey*
  • Isolate key management and usage in dedicated projects
  • Require approvals, log all key access, and monitor
  • Avoid broad roles/owner on key scopes

Remediation

CLI

gcloud projects remove-iam-policy-binding <PROJECT_ID> --member=<MEMBER> --role=roles/cloudkms.admin

Terraform

resource "google_project_iam_binding" "<example_resource_name>" {
  project = "<PROJECT_ID>"
  role    = "roles/cloudkms.admin" # Critical: ensure the offending principal is NOT bound as KMS Admin
  members = [
    "user:<ALLOWED_MEMBER_EMAIL>" # Critical: exclude any member who also has CryptoKey* roles to enforce separation of duties
  ]
}

Other

1In Google Cloud Console, go to IAM & Admin > IAM
2Locate the principal listed in the finding and click Edit principal
3Remove either "Cloud KMS Admin" or any of the "Cloud KMS CryptoKey Encrypter/Decrypter" roles from the project
4Click Save

Check Code

from prowler.lib.check.models import Check, Check_Report_GCP
from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_client import (
    cloudresourcemanager_client,
)


class iam_role_kms_enforce_separation_of_duties(Check):
    def execute(self) -> Check_Report_GCP:
        findings = []
        for project in cloudresourcemanager_client.project_ids:
            non_compliant_members = []
            kms_admin_members = []
            report = Check_Report_GCP(
                metadata=self.metadata(),
                resource=cloudresourcemanager_client.projects[project],
                project_id=project,
                location=cloudresourcemanager_client.region,
                resource_name=(
                    cloudresourcemanager_client.projects[project].name
                    if cloudresourcemanager_client.projects[project].name
                    else "GCP Project"
                ),
            )
            report.status = "PASS"
            report.status_extended = f"Principle of separation of duties was enforced for KMS-Related Roles in project {project}."
            for binding in cloudresourcemanager_client.bindings:
                if binding.project_id == project:
                    if "roles/cloudkms.admin" in binding.role:
                        kms_admin_members.extend(binding.members)
                    if (
                        "roles/cloudkms.cryptoKeyEncrypterDecrypter" in binding.role
                        or "roles/cloudkms.cryptoKeyEncrypter" in binding.role
                        or "roles/cloudkms.cryptoKeyDecrypter" in binding.role
                    ):
                        for member in binding.members:
                            if member in kms_admin_members:
                                non_compliant_members.append(member)
            if non_compliant_members:
                report.status = "FAIL"
                report.status_extended = f"Principle of separation of duties was not enforced for KMS-Related Roles in project {project} in members {','.join(non_compliant_members)}."
            findings.append(report)

        return findings