Back to Checks
gcp

Project enforces separation of duties for Service Account Admin and Service Account User roles

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

Google Cloud IAM policies are evaluated to find principals granted both roles/iam.serviceAccountAdmin and roles/iam.serviceAccountUser within a project. Service-account related roles are expected to be segregated so that service account lifecycle management is distinct from their use or impersonation.

Risk

With both roles, a principal can create or modify service accounts and then use or attach them to workloads, enabling unchecked impersonation. This endangers confidentiality (expanded data access), integrity (policy/workload changes), and availability (persistence or sabotage via privileged automation).

Run this check with Prowler

Run in Prowler Cloud
Run
prowler gcp --checks iam_role_sa_enforce_separation_of_duties

Recommendation

Enforce separation of duties: assign roles/iam.serviceAccountAdmin for lifecycle tasks and roles/iam.serviceAccountUser for attach/impersonate, never both to one principal.

  • Apply least privilege with narrow scope and conditions
  • Use temporary elevation/approvals
  • Regularly audit IAM bindings and logs

Remediation

Terraform

# Remove all project-level principals from Service Account User
resource "google_project_iam_binding" "sa_user_none" {
  project = "<example_resource_id>"
  role    = "roles/iam.serviceAccountUser"  # critical: target role to clear at project level
  members = []                                # critical: empty list removes the binding (no members)
}

# Remove all project-level principals from Service Account Admin
resource "google_project_iam_binding" "sa_admin_none" {
  project = "<example_resource_id>"
  role    = "roles/iam.serviceAccountAdmin"  # critical: target role to clear at project level
  members = []                                 # critical: empty list removes the binding (no members)
}

Other

1In Google Cloud Console, go to IAM & Admin > IAM
2Click the View by Role tab
3Select the role Service Account Admin (roles/iam.serviceAccountAdmin)
4Remove all listed principals from this role and click Save
5Select the role Service Account User (roles/iam.serviceAccountUser)
6Remove all listed principals from this role and click 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_sa_enforce_separation_of_duties(Check):
    def execute(self) -> Check_Report_GCP:
        findings = []
        for project in cloudresourcemanager_client.project_ids:
            non_compliant_members = []
            report = Check_Report_GCP(
                metadata=self.metadata(),
                resource=cloudresourcemanager_client.projects[project],
                location=cloudresourcemanager_client.region,
                project_id=project,
            )
            report.status = "PASS"
            report.status_extended = f"Principle of separation of duties was enforced for Service-Account Related Roles in project {project}."
            for binding in cloudresourcemanager_client.bindings:
                if binding.project_id == project and (
                    "roles/iam.serviceAccountUser" in binding.role
                    or "roles/iam.serviceAccountAdmin" in binding.role
                ):
                    non_compliant_members.extend(binding.members)
            if non_compliant_members:
                report.status = "FAIL"
                report.status_extended = f"Principle of separation of duties was not enforced for Service-Account Related Roles in project {project} in members {','.join(non_compliant_members)}."
            findings.append(report)

        return findings