> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Google.iam.admin.v1.undeleterole

### Event Information

* **Event meaning & context**
  * `google.iam.admin.v1.UndeleteRole` indicates that a previously *soft-deleted* **custom IAM role** has been restored (undeleted) in a project, folder, or organization using the IAM Admin API (`roles.undelete`).
  * This changes the role’s state back to active, making its permissions assignable to principals again via IAM bindings.

* **Security & compliance implications (e.g., ISO 27001, SOC 2, PCI, HIPAA)**
  * Undeleting a role can *re-enable deprecated or overly permissive permissions*, potentially violating least privilege requirements.
  * For regulated workloads, this event should be correlated with IAM policy changes and change-management tickets to ensure approval and proper justification are documented.

* **Practical actions in production**
  * Alert on this event in Cloud Logging / Cloud Monitoring and require review of:
    * Who performed the action, when, and from where (principal, IP, method).
    * The role definition (permissions list) and all members currently or previously bound to this role.
  * If unjustified, remove bindings to the role or re-delete the role, and tighten IAM admin privileges (e.g., restrict `iam.roles.undelete` via custom admin roles).

### Examples

* **Revival of deprecated high-privilege custom role**
  * A previously deleted custom role with broad permissions (e.g., `resourcemanager.*`, `iam.roles.*`) is undeleted and then re-bound to service accounts or users.
  * Impact: Privilege escalation or lateral movement; violates least-privilege principles (relevant to ISO 27001 A.9, CIS GCP, NIST AC-6).

* **Bypassing access-review / recertification decisions**
  * A role removed as part of a quarterly access review (e.g., to meet SOX or PCI-DSS requirement for periodic access recertification) is silently undeleted after the review closes.
  * Impact: Non-compliance with access-governance controls; difficult audit trail if `UndeleteRole` is not monitored and alerted on.

* **Reactivation of permissions for compromised identities**
  * After an incident, a role tied to a compromised service account/user is deleted as a containment step, but an attacker with `roles/iam.roleAdmin` runs `google.iam.admin.v1.UndeleteRole` to restore it.
  * Impact: Incident containment is negated, enabling attackers to regain access and persistence; conflicts with incident-response controls (e.g., NIST IR, ISO 27035).

### Remediation

#### Using Console

* **Immediately restrict who can undelete and bind roles (least privilege & controls)**
  * In GCP Console, go to **IAM & Admin → IAM**, filter for principals with `roles/iam.roleAdmin`, `roles/owner`, or any custom role including `iam.roles.undelete` / `iam.roles.update` / `resourcemanager.projects.setIamPolicy`.
  * Edit each principal’s permissions: click the pencil icon → **REMOVE** overly broad roles → instead assign narrowly scoped roles (e.g., `roles/iam.securityReviewer`, `roles/iam.viewer`) that do not allow role admin or policy changes.
  * Repeat at **Organization**, **Folder**, and **Project** levels to enforce least privilege aligned with ISO 27001 A.9, NIST AC-6, and CIS GCP benchmarks.

* **Harden custom role lifecycle and access-review process (prevent revival after recertification or incidents)**
  * In **IAM & Admin → Roles**, locate the high-privilege custom role (status “Deleted” or recently changed) → if it must not be used again, ensure it is **fully deleted** (after 7‑day soft-delete window) and recreate a new, least‑privilege role from scratch with only required permissions; document its intended use and approvers.
  * For roles removed during access reviews or incident response, implement a change-control policy: any role creation / update / undelete requires a documented ticket and multi-party approval (e.g., security + system owner) before being applied in the Console.
  * In **IAM & Admin → Audit Logs**, enable **Admin Activity** logs for `iam.googleapis.com` at Org level and send logs to Cloud Logging & SIEM; configure log-based alerts on `google.iam.admin.v1.UndeleteRole` and `SetIamPolicy` so any post-review or post-incident resurrection of roles triggers investigation (supports SOX, PCI-DSS, NIST IR, ISO 27035).

* **Operational containment when a risky role has been revived (practical GCP Console steps)**
  * Detect and inspect: in **IAM & Admin → Roles**, filter by **Custom** and sort by **Last modified**; click the suspicious role → review **Permissions** and **Bindings** (via **IAM** page) to identify all users/service accounts that currently have it.
  * Contain access: in **IAM**, remove bindings for this role from all principals (edit each member → trash icon for the role) and, if necessary, temporarily disable or rotate keys/credentials for affected service accounts or users while incident response proceeds.
  * Post-incident: create a locked-down “break-glass” pattern (custom role with minimal admin perms, stored under Org control, only bindable via a documented emergency process) and add periodic reviews of **IAM → Roles** and **IAM → IAM Recommender** to ensure old high‑privilege roles are not silently reintroduced.

#### Using CLI

* **Immediately quarantine and replace the resurrected role**
  * List and inspect undeleted custom role (including permissions and bindings):
    * ```bash theme={null}
      gcloud iam roles describe roles/myHighPrivRole --project=PROJECT_ID
      gcloud projects get-iam-policy PROJECT_ID --filter="bindings.role:roles/myHighPrivRole" --format=yaml
      ```
  * Block further use: remove bindings and disable the role (do not just delete yet for forensics/compliance):
    * ```bash theme={null}
      # Remove role from a member
      gcloud projects remove-iam-policy-binding PROJECT_ID \
        --member="serviceAccount:SA_NAME@PROJECT_ID.iam.gserviceaccount.com" \
        --role="roles/myHighPrivRole"

      # Disable the custom role
      gcloud iam roles update roles/myHighPrivRole \
        --project=PROJECT_ID \
        --stage=DISABLED
      ```
  * Recreate least‑privilege replacements and migrate bindings:
    * ```bash theme={null}
      gcloud iam roles create roles/sa.leastPriv \
        --project=PROJECT_ID \
        --title="SA Least Privilege" \
        --stage=GA \
        --permissions="resourcemanager.projects.get,iam.serviceAccounts.actAs"
      ```

* **Harden governance and monitoring for `UndeleteRole` to meet ISO 27001 / CIS / NIST / SOX / PCI**
  * Enable Data Access and Admin Activity logs on `iam.googleapis.com` and create alerting on `google.iam.admin.v1.UndeleteRole` and high‑risk `SetIamPolicy`/`SetRole` calls:
    * ```bash theme={null}
      gcloud logging sinks create iam-admin-sink \
        storage.googleapis.com://GCS_LOG_BUCKET \
        --log-filter='resource.type="iam_role" OR protoPayload.methodName:"UndeleteRole"'
      ```
  * Restrict `roles/iam.roleAdmin` and `resourcemanager.*` to tightly controlled break‑glass groups; use org‑policies and conditional bindings:
    * ```bash theme={null}
      gcloud resource-manager org-policies enable-enforce \
        constraints/iam.disableServiceAccountKeyCreation \
        --organization=ORG_ID

      gcloud organizations add-iam-policy-binding ORG_ID \
        --member="group:breakglass-admins@org.com" \
        --role="roles/iam.roleAdmin" \
        --condition="expression=request.time<timestamp('2026-01-31T00:00:00Z'),title=TempAdmin,description=Time bound"
      ```
  * Embed undelete checks into access recertification: export role history and compare against review decisions:
    * ```bash theme={null}
      gcloud logging read \
        'protoPayload.methodName="google.iam.admin.v1.UndeleteRole"' \
        --project=PROJECT_ID \
        --format=json > undelete_role_events.json
      ```

* **Integrate with incident response: ensure deleted roles stay dead after compromise**
  * During IR, explicitly revoke admin capabilities that could resurrect access (e.g. temporarily strip `roles/iam.roleAdmin` from all but IR leads):
    * ```bash theme={null}
      gcloud organizations remove-iam-policy-binding ORG_ID \
        --member="group:infra-admins@org.com" \
        --role="roles/iam.roleAdmin"
      ```
  * After deleting or disabling roles tied to compromised identities, validate no resurrection and continuously watch for it:
    * ```bash theme={null}
      # Verify role is disabled or deleted
      gcloud iam roles describe roles/compromisedRole --project=PROJECT_ID || echo "Role deleted"

      # Continuous check (e.g., via CI job / cron)
      gcloud logging read \
        'protoPayload.methodName="google.iam.admin.v1.UndeleteRole" AND protoPayload.resourceName:"roles/compromisedRole"' \
        --project=PROJECT_ID \
        --freshness=1h --limit=1
      ```
  * Replace role-based containment with identity containment: rotate keys, disable accounts, and move workloads to new service accounts/roles while keeping tampered roles permanently disabled, satisfying NIST IR and ISO 27035 expectations.

#### Using Python

* **Detect and alert on `UndeleteRole` + risky permissions (guardrail & monitoring)**
  * Enable Cloud Audit Logs for `google.iam.admin.v1.UndeleteRole` and export to BigQuery / SIEM; build detections for custom roles with broad permissions (e.g., `resourcemanager.*`, `iam.roles.*`, `*Admin`, `*Owner`).
  * Example Python detection (BQ query runner) to list high‑risk undeleted roles in the last 24h and send to a webhook/alerting system:
    ```python theme={null}
    from google.cloud import bigquery
    import requests, json, datetime

    PROJECT_ID = "your-log-project"
    DATASET = "cloudaudit"
    TABLE = "cloudaudit_googleapis_com_activity"
    ALERT_WEBHOOK = "https://your-alert-endpoint"

    def query_undelete_high_priv_roles():
        client = bigquery.Client(project=PROJECT_ID)
        yesterday = (datetime.datetime.utcnow() - datetime.timedelta(days=1)).isoformat("T") + "Z"
        query = f"""
        SELECT
          protopayload_auditlog.authenticationInfo.principalEmail AS actor,
          protopayload_auditlog.requestMetadata.callerIp AS caller_ip,
          JSON_VALUE(protopayload_auditlog.request, '$.name') AS role_name,
          protopayload_auditlog.resourceName AS resource_name,
          protopayload_auditlog.request AS request_json,
          protopayload_auditlog.serviceData AS service_data,
          timestamp
        FROM `{PROJECT_ID}.{DATASET}.{TABLE}`
        WHERE
          protopayload_auditlog.methodName = "google.iam.admin.v1.UndeleteRole"
          AND timestamp >= TIMESTAMP("{yesterday}")
        """
        return client.query(query).result()

    def is_high_priv_role(role_def: dict) -> bool:
        perms = role_def.get("includedPermissions", [])
        risky_prefixes = ("resourcemanager.", "iam.roles.", "iam.serviceAccounts.", "iam.serviceAccountKeys.")
        admin_suffixes = (".setIamPolicy", ".getIamPolicy", ".update", ".delete", "Admin", "Owner")
        for p in perms:
            if p == "*" or p.startswith(risky_prefixes) or any(p.endswith(s) for s in admin_suffixes):
                return True
        return False

    def get_custom_role(project_id: str, role_name: str):
        from google.cloud import iam_v1
        client = iam_v1.IAMClient()
        request = iam_v1.GetRoleRequest(name=role_name)
        return client.get_role(request=request)

    def alert(payload):
        requests.post(ALERT_WEBHOOK, data=json.dumps(payload), headers={"Content-Type": "application/json"})

    def main(event=None, context=None):
        # Intended to be run as Cloud Function with proper IAM scopes
        rows = query_undelete_high_priv_roles()
        for row in rows:
            role_name = row.role_name or row.resource_name
            if not role_name:
                continue
            try:
                role = get_custom_role(PROJECT_ID, role_name)
            except Exception:
                continue
            if is_high_priv_role(role._pb):  # low-level access; or map to dict as needed
                alert({
                    "type": "HIGH_PRIV_ROLE_UNDELETED",
                    "role_name": role_name,
                    "actor": row.actor,
                    "caller_ip": row.caller_ip,
                    "timestamp": str(row.timestamp),
                    "reference": {
                        "controls": ["ISO 27001 A.9", "CIS GCP IAM", "NIST AC-6", "SOX/PCI-DSS recertification"]
                    }
                })
    ```

* **Automated remediation of revived / non‑approved roles (least‑privilege & recertification)**
  * Maintain an allow‑list of approved custom roles and a configuration source of truth (e.g., YAML in Git). Any undeleted custom role not in the allow‑list, or that conflicts with recertification decisions, should be auto‑disabled or deleted, and any new bindings removed.
  * Example Python Cloud Function to auto‑disable / delete non‑approved undeleted roles and strip bindings (triggered by Pub/Sub from Audit Logs):
    ```python theme={null}
    import base64, json, os
    from google.cloud import iam_v1
    from googleapiclient.discovery import build

    APPROVED_ROLES = set([
        # Fully qualified custom role names that are allowed to exist
        "projects/your-project/roles/ApprovedCustomRole1",
        "organizations/1234567890/roles/ApprovedOrgRole"
    ])
    AUTO_DELETE = True  # if False, will disable only

    def _get_resource_context(resource_name: str):
        # resource_name examples:
        # "projects/your-project/roles/CustomRole"
        # "organizations/1234567890/roles/CustomRole"
        if resource_name.startswith("projects/"):
            return "project", resource_name.split("/")[1]
        if resource_name.startswith("organizations/"):
            return "org", resource_name.split("/")[1]
        return None, None

    def undelete_role_remediator(event, context):
        # Pub/Sub-triggered with AuditLog as payload
        data = base64.b64decode(event["data"]).decode("utf-8")
        log_entry = json.loads(data)

        method = log_entry.get("protoPayload", {}).get("methodName")
        if method != "google.iam.admin.v1.UndeleteRole":
            return

        resource_name = log_entry.get("protoPayload", {}).get("resourceName")
        if not resource_name:
            return

        if resource_name in APPROVED_ROLES:
            return  # compliant

        iam_client = iam_v1.IAMClient()
        role_req = iam_v1.GetRoleRequest(name=resource_name)
        try:
            role = iam_client.get_role(request=role_req)
        except Exception:
            return

        # Disable or delete high‑risk / non‑approved role
        if AUTO_DELETE:
            del_req = iam_v1.DeleteRoleRequest(name=resource_name)
            iam_client.delete_role(request=del_req)
        else:
            role.disabled = True
            upd_req = iam_v1.UpdateRoleRequest(role=role)
            iam_client.update_role(request=upd_req)

        # Remove role bindings to prevent escalation / persistence
        scope_type, scope_id = _get_resource_context(resource_name)
        if scope_type == "project":
            crm = build("cloudresourcemanager", "v1", cache_discovery=False)
            policy = crm.projects().getIamPolicy(
                resource=scope_id, body={"options": {"requestedPolicyVersion": 3}}
            ).execute()
            bindings = policy.get("bindings", [])
            new_bindings = [b for b in bindings if b.get("role") != resource_name]
            if len(new_bindings) != len(bindings):
                policy["bindings"] = new_bindings
                crm.projects().setIamPolicy(
                    resource=scope_id, body={"policy": policy}
                ).execute()
        # For org-level roles, use cloudresourcemanager v1 organizations().getIamPolicy/setIamPolicy

        # Optionally, write to a security log / ticketing system to support SOX/PCI recertification evidence
    ```

* **Governance & hardening to prevent bypass and incident‑response regression**
  * Restrict `roles/iam.roleAdmin`, `roles/iam.organizationRoleAdmin`, and `resourcemanager.*` to a tightly controlled break‑glass group; require approvals (e.g., Access Context Manager + PAM) and log justification for use.
  * As part of incident response (NIST IR / ISO 27035), script permanent revocation for compromised identities by removing their IAM bindings and marking tied roles as “blocked” in your config repo so any `UndeleteRole` attempt is automatically reverted. Example helper using Python to strip a custom role from a specific compromised principal across the project:
    ```python theme={null}
    from googleapiclient.discovery import build

    def remove_role_from_principal(project_id, role_name, principal):
        crm = build("cloudresourcemanager", "v1", cache_discovery=False)
        policy = crm.projects().getIamPolicy(
            resource=project_id, body={"options": {"requestedPolicyVersion": 3}}
        ).execute()
        bindings = policy.get("bindings", [])
        changed = False
        for b in bindings:
            if b.get("role") == role_name and principal in b.get("members", []):
                b["members"].remove(principal)
                changed = True
        if changed:
            crm.projects().setIamPolicy(
                resource=project_id, body={"policy": policy}
            ).execute()

    # Example usage:
    # remove_role_from_principal("prod-project", "projects/prod-project/roles/LegacyHighPrivRole", "serviceAccount:compromised@proj.iam.gserviceaccount.com")
    ```
