[{"data":1,"prerenderedAt":2396},["ShallowReactive",2],{"post:\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization\u002F":3},{"post":4,"newer":2345,"older":2356,"related":2366,"series":2394},{"id":5,"title":6,"body":7,"canonical":2328,"categories":2329,"date":2332,"description":2333,"extension":2334,"featured":2335,"hero":2336,"image":2336,"meta":2337,"navigation":191,"path":2338,"readingTime":188,"seo":2339,"series":2336,"seriesOrder":2336,"sites":2340,"source":2336,"stem":2341,"tags":2342,"updated":2336,"url":2343,"__hash__":2344},"blog\u002Fblog\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization.md","Python: gcloud – Audit IAM Bindings Across a GCP Organization",{"type":8,"value":9,"toc":2320},"minimark",[10,23,64,69,123,134,225,229,331,335,341,435,438,489,492,527,530,538,541,547,551,2163,2167,2252,2256,2316],[11,12,13,14,18,19,22],"p",{},"IAM sprawl in a GCP organization tends to follow a predictable pattern: someone gets ",[15,16,17],"code",{},"roles\u002Feditor"," on a project during initial setup \"just to get it working,\" and it's never revisited. Multiply that across a few dozen projects under an org and you have a real exposure that no single project's IAM page will ever show you, because allow policies are attached per resource (organization, folder, project, bucket, dataset, and so on). Cloud Asset Inventory's ",[15,20,21],{},"SearchAllIamPolicies"," API is the one place that answers \"who has been granted what, where\" across an entire organization in a single paginated call. This script wraps that call, flattens the result into one row per principal-role-resource binding, and flags the things I care about most on a first pass:",[24,25,26,44,55,58],"ul",{},[27,28,29,30,33,34,36,37,33,40,43],"li",{},"the write-capable basic roles (legacy ",[15,31,32],{},"roles\u002Fowner"," and ",[15,35,17],{},", and their newer counterparts ",[15,38,39],{},"roles\u002Fadmin",[15,41,42],{},"roles\u002Fwriter",") granted to a user or group. Google's own guidance is blunt: basic roles \"include thousands of permissions across all Google Cloud services,\" and in production you shouldn't grant them unless there is no alternative;",[27,45,46,47,50,51,54],{},"public bindings to ",[15,48,49],{},"allUsers"," or ",[15,52,53],{},"allAuthenticatedUsers",";",[27,56,57],{},"users or groups outside your own domain;",[27,59,60,63],{},[15,61,62],{},"deleted:"," principals, which are grants left behind after the identity was removed and are pure clutter.",[65,66,68],"h2",{"id":67},"requirements","Requirements",[24,70,71,74,85,92,102,112],{},[27,72,73],{},"Python 3.9 or later.",[27,75,76,77,80,81,84],{},"The ",[15,78,79],{},"google-cloud-asset"," package (",[15,82,83],{},"pip install google-cloud-asset",").",[27,86,87,88,91],{},"Application Default Credentials (",[15,89,90],{},"gcloud auth application-default login"," for interactive use, or an attached service account when this runs on a schedule). Avoid a downloaded service account key for this; an org-wide read of every IAM policy is exactly the kind of credential you don't want sitting in a file.",[27,93,94,97,98,101],{},[15,95,96],{},"cloudasset.assets.searchAllIamPolicies"," on the organization. The predefined Cloud Asset Viewer role (",[15,99,100],{},"roles\u002Fcloudasset.viewer",") granted at the organization node includes it.",[27,103,104,107,108,111],{},[15,105,106],{},"serviceusage.services.use"," on the project the calls are billed to (the quota project). The Cloud Asset Inventory docs state that all Cloud Asset Inventory calls require it; ",[15,109,110],{},"roles\u002Fserviceusage.serviceUsageConsumer"," on that project covers it.",[27,113,114,115,118,119,122],{},"The Cloud Asset API (",[15,116,117],{},"cloudasset.googleapis.com",") enabled on that quota project: ",[15,120,121],{},"gcloud services enable cloudasset.googleapis.com --project=my-audit-project",".",[11,124,125,126,129,130,133],{},"Grant the two roles to whoever runs it (swap ",[15,127,128],{},"user:"," for ",[15,131,132],{},"serviceAccount:"," for a scheduled job):",[135,136,141],"pre",{"className":137,"code":138,"language":139,"meta":140,"style":140},"language-bash shiki shiki-themes github-dark","gcloud organizations add-iam-policy-binding 123456789012 \\\n  --member='user:auditor@example.com' \\\n  --role='roles\u002Fcloudasset.viewer'\n\ngcloud projects add-iam-policy-binding my-audit-project \\\n  --member='user:auditor@example.com' \\\n  --role='roles\u002Fserviceusage.serviceUsageConsumer'\n","bash","",[15,142,143,166,177,186,193,208,217],{"__ignoreMap":140},[144,145,148,152,156,159,163],"span",{"class":146,"line":147},"line",1,[144,149,151],{"class":150},"svObZ","gcloud",[144,153,155],{"class":154},"sU2Wk"," organizations",[144,157,158],{"class":154}," add-iam-policy-binding",[144,160,162],{"class":161},"sDLfK"," 123456789012",[144,164,165],{"class":161}," \\\n",[144,167,169,172,175],{"class":146,"line":168},2,[144,170,171],{"class":161},"  --member=",[144,173,174],{"class":154},"'user:auditor@example.com'",[144,176,165],{"class":161},[144,178,180,183],{"class":146,"line":179},3,[144,181,182],{"class":161},"  --role=",[144,184,185],{"class":154},"'roles\u002Fcloudasset.viewer'\n",[144,187,189],{"class":146,"line":188},4,[144,190,192],{"emptyLinePlaceholder":191},true,"\n",[144,194,196,198,201,203,206],{"class":146,"line":195},5,[144,197,151],{"class":150},[144,199,200],{"class":154}," projects",[144,202,158],{"class":154},[144,204,205],{"class":154}," my-audit-project",[144,207,165],{"class":161},[144,209,211,213,215],{"class":146,"line":210},6,[144,212,171],{"class":161},[144,214,174],{"class":154},[144,216,165],{"class":161},[144,218,220,222],{"class":146,"line":219},7,[144,221,182],{"class":161},[144,223,224],{"class":154},"'roles\u002Fserviceusage.serviceUsageConsumer'\n",[65,226,228],{"id":227},"parameters","Parameters",[230,231,232,248],"table",{},[233,234,235],"thead",{},[236,237,238,242,245],"tr",{},[239,240,241],"th",{},"Parameter",[239,243,244],{},"Required",[239,246,247],{},"Description",[249,250,251,269,281,296,316],"tbody",{},[236,252,253,259,262],{},[254,255,256],"td",{},[15,257,258],{},"--organization",[254,260,261],{},"Yes",[254,263,264,265,268],{},"Numeric organization ID (",[15,266,267],{},"gcloud organizations list"," shows it).",[236,270,271,276,278],{},[254,272,273],{},[15,274,275],{},"--quota-project",[254,277,261],{},[254,279,280],{},"Project ID the API calls are billed and quota-checked against.",[236,282,283,288,290],{},[254,284,285],{},[15,286,287],{},"--domain",[254,289,261],{},[254,291,292,293,84],{},"Approved domain. Can be repeated (",[15,294,295],{},"--domain example.com --domain example.org",[236,297,298,303,306],{},[254,299,300],{},[15,301,302],{},"--query",[254,304,305],{},"No",[254,307,308,309,50,312,315],{},"Optional Cloud Asset Inventory query, for example ",[15,310,311],{},"memberTypes:user",[15,313,314],{},"roles:roles\u002Fowner",", to narrow the search server-side.",[236,317,318,323,325],{},[254,319,320],{},[15,321,322],{},"--output",[254,324,305],{},[254,326,327,328,122],{},"CSV path. Defaults to ",[15,329,330],{},"iam-audit.csv",[65,332,334],{"id":333},"usage","Usage",[11,336,337,338,340],{},"Before running the script, it's worth knowing that the same API is available from ",[15,339,151],{},", which is handy for a single question:",[135,342,344],{"className":137,"code":343,"language":139,"meta":140,"style":140},"# Every resource where anyone holds roles\u002Fowner\ngcloud asset search-all-iam-policies \\\n  --scope=organizations\u002F123456789012 \\\n  --query='roles:roles\u002Fowner' \\\n  --flatten='policy.bindings[].members[]' \\\n  --format='csv(resource,policy.bindings.role,policy.bindings.members)'\n\n# Everything granted to one person, anywhere in the org\ngcloud asset search-all-iam-policies \\\n  --scope=organizations\u002F123456789012 \\\n  --query='policy:\"user:alex@example.com\"'\n",[15,345,346,352,364,371,381,391,399,403,409,420,427],{"__ignoreMap":140},[144,347,348],{"class":146,"line":147},[144,349,351],{"class":350},"sAwPA","# Every resource where anyone holds roles\u002Fowner\n",[144,353,354,356,359,362],{"class":146,"line":168},[144,355,151],{"class":150},[144,357,358],{"class":154}," asset",[144,360,361],{"class":154}," search-all-iam-policies",[144,363,165],{"class":161},[144,365,366,369],{"class":146,"line":179},[144,367,368],{"class":161},"  --scope=organizations\u002F123456789012",[144,370,165],{"class":161},[144,372,373,376,379],{"class":146,"line":188},[144,374,375],{"class":161},"  --query=",[144,377,378],{"class":154},"'roles:roles\u002Fowner'",[144,380,165],{"class":161},[144,382,383,386,389],{"class":146,"line":195},[144,384,385],{"class":161},"  --flatten=",[144,387,388],{"class":154},"'policy.bindings[].members[]'",[144,390,165],{"class":161},[144,392,393,396],{"class":146,"line":210},[144,394,395],{"class":161},"  --format=",[144,397,398],{"class":154},"'csv(resource,policy.bindings.role,policy.bindings.members)'\n",[144,400,401],{"class":146,"line":219},[144,402,192],{"emptyLinePlaceholder":191},[144,404,406],{"class":146,"line":405},8,[144,407,408],{"class":350},"# Everything granted to one person, anywhere in the org\n",[144,410,412,414,416,418],{"class":146,"line":411},9,[144,413,151],{"class":150},[144,415,358],{"class":154},[144,417,361],{"class":154},[144,419,165],{"class":161},[144,421,423,425],{"class":146,"line":422},10,[144,424,368],{"class":161},[144,426,165],{"class":161},[144,428,430,432],{"class":146,"line":429},11,[144,431,375],{"class":161},[144,433,434],{"class":154},"'policy:\"user:alex@example.com\"'\n",[11,436,437],{},"The script does the same search without a filter and applies all the checks in one pass. Run it against the whole organization and write a CSV:",[135,439,441],{"className":137,"code":440,"language":139,"meta":140,"style":140},"python audit_iam_bindings.py \\\n  --organization 123456789012 \\\n  --quota-project my-audit-project \\\n  --domain example.com \\\n  --output iam-audit.csv\n",[15,442,443,453,462,471,481],{"__ignoreMap":140},[144,444,445,448,451],{"class":146,"line":147},[144,446,447],{"class":150},"python",[144,449,450],{"class":154}," audit_iam_bindings.py",[144,452,165],{"class":161},[144,454,455,458,460],{"class":146,"line":168},[144,456,457],{"class":161},"  --organization",[144,459,162],{"class":161},[144,461,165],{"class":161},[144,463,464,467,469],{"class":146,"line":179},[144,465,466],{"class":161},"  --quota-project",[144,468,205],{"class":154},[144,470,165],{"class":161},[144,472,473,476,479],{"class":146,"line":188},[144,474,475],{"class":161},"  --domain",[144,477,478],{"class":154}," example.com",[144,480,165],{"class":161},[144,482,483,486],{"class":146,"line":195},[144,484,485],{"class":161},"  --output",[144,487,488],{"class":154}," iam-audit.csv\n",[11,490,491],{},"Narrow the search server-side to human and group principals only:",[135,493,495],{"className":137,"code":494,"language":139,"meta":140,"style":140},"python audit_iam_bindings.py --organization 123456789012 --quota-project my-audit-project \\\n  --domain example.com --query 'memberTypes:user OR memberTypes:group'\n",[15,496,497,515],{"__ignoreMap":140},[144,498,499,501,503,506,508,511,513],{"class":146,"line":147},[144,500,447],{"class":150},[144,502,450],{"class":154},[144,504,505],{"class":161}," --organization",[144,507,162],{"class":161},[144,509,510],{"class":161}," --quota-project",[144,512,205],{"class":154},[144,514,165],{"class":161},[144,516,517,519,521,524],{"class":146,"line":168},[144,518,475],{"class":161},[144,520,478],{"class":154},[144,522,523],{"class":161}," --query",[144,525,526],{"class":154}," 'memberTypes:user OR memberTypes:group'\n",[11,528,529],{},"Sample output (illustrative):",[135,531,536],{"className":532,"code":534,"language":535,"meta":140},[533],"language-text","Searching organizations\u002F123456789012 for IAM allow policies ...\nRetrieved 1,842 bindings across 96 resources.\n  basic role to user\u002Fgroup ........ 7\n  public (allUsers\u002FallAuth) ....... 1\n  external identity ............... 3\n  deleted principal ............... 12\nReport written to iam-audit.csv\n","text",[15,537,534],{"__ignoreMap":140},[11,539,540],{},"And the CSV rows it produces:",[135,542,545],{"className":543,"code":544,"language":535,"meta":140},[533],"resource,asset_type,project,role,member,condition,flagged,flag_reason\n\u002F\u002Fcloudresourcemanager.googleapis.com\u002Fprojects\u002Fweb-prod,cloudresourcemanager.googleapis.com\u002FProject,projects\u002F111111111111,roles\u002Feditor,user:jane@example.com,,True,basic role (roles\u002Feditor) granted to a user or group\n\u002F\u002Fstorage.googleapis.com\u002Fpublic-assets,storage.googleapis.com\u002FBucket,projects\u002F222222222222,roles\u002Fstorage.objectViewer,allUsers,,True,public binding (allUsers)\n\u002F\u002Fcloudresourcemanager.googleapis.com\u002Ffolders\u002F333333333333,cloudresourcemanager.googleapis.com\u002FFolder,,roles\u002Fviewer,user:contractor@partner.example,,True,external identity (partner.example)\n",[15,546,544],{"__ignoreMap":140},[65,548,550],{"id":549},"script","Script",[135,552,555],{"className":553,"code":554,"language":447,"meta":140,"style":140},"language-python shiki shiki-themes github-dark","\"\"\"\naudit_iam_bindings.py\n\nAudits IAM allow policies across a GCP organization using Cloud Asset\nInventory's SearchAllIamPolicies API. Flattens the results to one row per\nprincipal-role-resource binding and flags:\n  - basic roles (roles\u002Fowner, roles\u002Feditor, roles\u002Fadmin, roles\u002Fwriter) granted\n    to users or groups\n  - public bindings (allUsers, allAuthenticatedUsers)\n  - users or groups outside the approved domain(s)\n  - deleted principals left behind in policies\n\nReads:  the allow policies attached to every resource in the organization.\nWrites: a CSV report at --output with every binding plus flagged\u002Fflag_reason.\n\nRequires roles\u002Fcloudasset.viewer on the organization and\nserviceusage.services.use on --quota-project.\n\"\"\"\n\nimport argparse\nimport csv\nimport sys\nfrom collections import Counter\n\nfrom google.api_core.client_options import ClientOptions\nfrom google.api_core.exceptions import GoogleAPICallError\nfrom google.cloud import asset_v1\n\nBASIC_ROLES = {\"roles\u002Fowner\", \"roles\u002Feditor\", \"roles\u002Fadmin\", \"roles\u002Fwriter\"}\nPUBLIC_MEMBERS = {\"allUsers\", \"allAuthenticatedUsers\"}\nFIELDS = [\"resource\", \"asset_type\", \"project\", \"role\", \"member\", \"condition\", \"flagged\", \"flag_reason\"]\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description=\"Audit IAM bindings across a GCP organization.\")\n    parser.add_argument(\"--organization\", required=True, help=\"Organization ID, e.g. 123456789012\")\n    parser.add_argument(\"--quota-project\", required=True, help=\"Project ID to bill the API calls against\")\n    parser.add_argument(\"--domain\", required=True, action=\"append\", help=\"Approved domain; repeatable\")\n    parser.add_argument(\"--query\", default=\"\", help=\"Optional Cloud Asset Inventory query string\")\n    parser.add_argument(\"--output\", default=\"iam-audit.csv\", help=\"Path to the output CSV\")\n    return parser.parse_args()\n\n\ndef flag_binding(member, role, approved_domains):\n    \"\"\"Returns (category, reason) for a flagged binding, or (None, \"\").\"\"\"\n    if member in PUBLIC_MEMBERS:\n        return \"public\", f\"public binding ({member})\"\n\n    if member.startswith(\"deleted:\"):\n        return \"deleted\", \"deleted principal still bound\"\n\n    is_user_or_group = member.startswith(\"user:\") or member.startswith(\"group:\")\n\n    if is_user_or_group and role in BASIC_ROLES:\n        return \"basic\", f\"basic role ({role}) granted to a user or group\"\n\n    if is_user_or_group:\n        domain = member.split(\":\", 1)[1].split(\"@\")[-1].lower()\n        if domain not in approved_domains:\n            return \"external\", f\"external identity ({domain})\"\n\n    return None, \"\"\n\n\ndef main():\n    args = parse_args()\n    approved = {d.lower() for d in args.domain}\n\n    client = asset_v1.AssetServiceClient(\n        client_options=ClientOptions(quota_project_id=args.quota_project)\n    )\n    scope = f\"organizations\u002F{args.organization}\"\n    request = asset_v1.SearchAllIamPoliciesRequest(scope=scope, query=args.query, page_size=500)\n\n    print(f\"Searching {scope} for IAM allow policies ...\")\n\n    rows = []\n    counts = Counter()\n    resources = 0\n\n    try:\n        for result in client.search_all_iam_policies(request=request):\n            resources += 1\n            for binding in result.policy.bindings:\n                condition = binding.condition.title if binding.condition.expression else \"\"\n                for member in binding.members:\n                    category, reason = flag_binding(member, binding.role, approved)\n                    if category:\n                        counts[category] += 1\n                    rows.append(\n                        {\n                            \"resource\": result.resource,\n                            \"asset_type\": result.asset_type,\n                            \"project\": result.project,\n                            \"role\": binding.role,\n                            \"member\": member,\n                            \"condition\": condition,\n                            \"flagged\": bool(category),\n                            \"flag_reason\": reason,\n                        }\n                    )\n    except GoogleAPICallError as exc:\n        print(f\"Cloud Asset Inventory call failed: {exc}\", file=sys.stderr)\n        sys.exit(1)\n\n    print(f\"Retrieved {len(rows):,} bindings across {resources:,} resources.\")\n    print(f\"  basic role to user\u002Fgroup ........ {counts['basic']}\")\n    print(f\"  public (allUsers\u002FallAuth) ....... {counts['public']}\")\n    print(f\"  external identity ............... {counts['external']}\")\n    print(f\"  deleted principal ............... {counts['deleted']}\")\n\n    with open(args.output, \"w\", newline=\"\", encoding=\"utf-8\") as csv_file:\n        writer = csv.DictWriter(csv_file, fieldnames=FIELDS)\n        writer.writeheader()\n        writer.writerows(rows)\n\n    print(f\"Report written to {args.output}\")\n\n\nif __name__ == \"__main__\":\n    main()\n",[15,556,557,562,567,571,576,581,586,591,596,601,606,611,616,622,628,633,639,645,650,655,666,674,682,696,701,714,727,740,745,779,799,851,856,861,873,897,928,955,992,1021,1049,1058,1063,1068,1079,1085,1103,1132,1137,1151,1164,1169,1195,1200,1221,1246,1251,1259,1299,1317,1342,1347,1360,1365,1370,1380,1391,1413,1418,1429,1448,1454,1478,1515,1520,1545,1550,1561,1572,1583,1588,1596,1618,1630,1644,1667,1680,1691,1700,1710,1716,1722,1731,1740,1749,1758,1767,1776,1791,1800,1806,1812,1827,1860,1870,1875,1915,1944,1971,1998,2025,2030,2071,2091,2097,2103,2108,2131,2136,2141,2157],{"__ignoreMap":140},[144,558,559],{"class":146,"line":147},[144,560,561],{"class":154},"\"\"\"\n",[144,563,564],{"class":146,"line":168},[144,565,566],{"class":154},"audit_iam_bindings.py\n",[144,568,569],{"class":146,"line":179},[144,570,192],{"emptyLinePlaceholder":191},[144,572,573],{"class":146,"line":188},[144,574,575],{"class":154},"Audits IAM allow policies across a GCP organization using Cloud Asset\n",[144,577,578],{"class":146,"line":195},[144,579,580],{"class":154},"Inventory's SearchAllIamPolicies API. Flattens the results to one row per\n",[144,582,583],{"class":146,"line":210},[144,584,585],{"class":154},"principal-role-resource binding and flags:\n",[144,587,588],{"class":146,"line":219},[144,589,590],{"class":154},"  - basic roles (roles\u002Fowner, roles\u002Feditor, roles\u002Fadmin, roles\u002Fwriter) granted\n",[144,592,593],{"class":146,"line":405},[144,594,595],{"class":154},"    to users or groups\n",[144,597,598],{"class":146,"line":411},[144,599,600],{"class":154},"  - public bindings (allUsers, allAuthenticatedUsers)\n",[144,602,603],{"class":146,"line":422},[144,604,605],{"class":154},"  - users or groups outside the approved domain(s)\n",[144,607,608],{"class":146,"line":429},[144,609,610],{"class":154},"  - deleted principals left behind in policies\n",[144,612,614],{"class":146,"line":613},12,[144,615,192],{"emptyLinePlaceholder":191},[144,617,619],{"class":146,"line":618},13,[144,620,621],{"class":154},"Reads:  the allow policies attached to every resource in the organization.\n",[144,623,625],{"class":146,"line":624},14,[144,626,627],{"class":154},"Writes: a CSV report at --output with every binding plus flagged\u002Fflag_reason.\n",[144,629,631],{"class":146,"line":630},15,[144,632,192],{"emptyLinePlaceholder":191},[144,634,636],{"class":146,"line":635},16,[144,637,638],{"class":154},"Requires roles\u002Fcloudasset.viewer on the organization and\n",[144,640,642],{"class":146,"line":641},17,[144,643,644],{"class":154},"serviceusage.services.use on --quota-project.\n",[144,646,648],{"class":146,"line":647},18,[144,649,561],{"class":154},[144,651,653],{"class":146,"line":652},19,[144,654,192],{"emptyLinePlaceholder":191},[144,656,658,662],{"class":146,"line":657},20,[144,659,661],{"class":660},"snl16","import",[144,663,665],{"class":664},"s95oV"," argparse\n",[144,667,669,671],{"class":146,"line":668},21,[144,670,661],{"class":660},[144,672,673],{"class":664}," csv\n",[144,675,677,679],{"class":146,"line":676},22,[144,678,661],{"class":660},[144,680,681],{"class":664}," sys\n",[144,683,685,688,691,693],{"class":146,"line":684},23,[144,686,687],{"class":660},"from",[144,689,690],{"class":664}," collections ",[144,692,661],{"class":660},[144,694,695],{"class":664}," Counter\n",[144,697,699],{"class":146,"line":698},24,[144,700,192],{"emptyLinePlaceholder":191},[144,702,704,706,709,711],{"class":146,"line":703},25,[144,705,687],{"class":660},[144,707,708],{"class":664}," google.api_core.client_options ",[144,710,661],{"class":660},[144,712,713],{"class":664}," ClientOptions\n",[144,715,717,719,722,724],{"class":146,"line":716},26,[144,718,687],{"class":660},[144,720,721],{"class":664}," google.api_core.exceptions ",[144,723,661],{"class":660},[144,725,726],{"class":664}," GoogleAPICallError\n",[144,728,730,732,735,737],{"class":146,"line":729},27,[144,731,687],{"class":660},[144,733,734],{"class":664}," google.cloud ",[144,736,661],{"class":660},[144,738,739],{"class":664}," asset_v1\n",[144,741,743],{"class":146,"line":742},28,[144,744,192],{"emptyLinePlaceholder":191},[144,746,748,751,754,757,760,763,766,768,771,773,776],{"class":146,"line":747},29,[144,749,750],{"class":161},"BASIC_ROLES",[144,752,753],{"class":660}," =",[144,755,756],{"class":664}," {",[144,758,759],{"class":154},"\"roles\u002Fowner\"",[144,761,762],{"class":664},", ",[144,764,765],{"class":154},"\"roles\u002Feditor\"",[144,767,762],{"class":664},[144,769,770],{"class":154},"\"roles\u002Fadmin\"",[144,772,762],{"class":664},[144,774,775],{"class":154},"\"roles\u002Fwriter\"",[144,777,778],{"class":664},"}\n",[144,780,782,785,787,789,792,794,797],{"class":146,"line":781},30,[144,783,784],{"class":161},"PUBLIC_MEMBERS",[144,786,753],{"class":660},[144,788,756],{"class":664},[144,790,791],{"class":154},"\"allUsers\"",[144,793,762],{"class":664},[144,795,796],{"class":154},"\"allAuthenticatedUsers\"",[144,798,778],{"class":664},[144,800,802,805,807,810,813,815,818,820,823,825,828,830,833,835,838,840,843,845,848],{"class":146,"line":801},31,[144,803,804],{"class":161},"FIELDS",[144,806,753],{"class":660},[144,808,809],{"class":664}," [",[144,811,812],{"class":154},"\"resource\"",[144,814,762],{"class":664},[144,816,817],{"class":154},"\"asset_type\"",[144,819,762],{"class":664},[144,821,822],{"class":154},"\"project\"",[144,824,762],{"class":664},[144,826,827],{"class":154},"\"role\"",[144,829,762],{"class":664},[144,831,832],{"class":154},"\"member\"",[144,834,762],{"class":664},[144,836,837],{"class":154},"\"condition\"",[144,839,762],{"class":664},[144,841,842],{"class":154},"\"flagged\"",[144,844,762],{"class":664},[144,846,847],{"class":154},"\"flag_reason\"",[144,849,850],{"class":664},"]\n",[144,852,854],{"class":146,"line":853},32,[144,855,192],{"emptyLinePlaceholder":191},[144,857,859],{"class":146,"line":858},33,[144,860,192],{"emptyLinePlaceholder":191},[144,862,864,867,870],{"class":146,"line":863},34,[144,865,866],{"class":660},"def",[144,868,869],{"class":150}," parse_args",[144,871,872],{"class":664},"():\n",[144,874,876,879,882,885,889,891,894],{"class":146,"line":875},35,[144,877,878],{"class":664},"    parser ",[144,880,881],{"class":660},"=",[144,883,884],{"class":664}," argparse.ArgumentParser(",[144,886,888],{"class":887},"s9osk","description",[144,890,881],{"class":660},[144,892,893],{"class":154},"\"Audit IAM bindings across a GCP organization.\"",[144,895,896],{"class":664},")\n",[144,898,900,903,906,908,911,913,916,918,921,923,926],{"class":146,"line":899},36,[144,901,902],{"class":664},"    parser.add_argument(",[144,904,905],{"class":154},"\"--organization\"",[144,907,762],{"class":664},[144,909,910],{"class":887},"required",[144,912,881],{"class":660},[144,914,915],{"class":161},"True",[144,917,762],{"class":664},[144,919,920],{"class":887},"help",[144,922,881],{"class":660},[144,924,925],{"class":154},"\"Organization ID, e.g. 123456789012\"",[144,927,896],{"class":664},[144,929,931,933,936,938,940,942,944,946,948,950,953],{"class":146,"line":930},37,[144,932,902],{"class":664},[144,934,935],{"class":154},"\"--quota-project\"",[144,937,762],{"class":664},[144,939,910],{"class":887},[144,941,881],{"class":660},[144,943,915],{"class":161},[144,945,762],{"class":664},[144,947,920],{"class":887},[144,949,881],{"class":660},[144,951,952],{"class":154},"\"Project ID to bill the API calls against\"",[144,954,896],{"class":664},[144,956,958,960,963,965,967,969,971,973,976,978,981,983,985,987,990],{"class":146,"line":957},38,[144,959,902],{"class":664},[144,961,962],{"class":154},"\"--domain\"",[144,964,762],{"class":664},[144,966,910],{"class":887},[144,968,881],{"class":660},[144,970,915],{"class":161},[144,972,762],{"class":664},[144,974,975],{"class":887},"action",[144,977,881],{"class":660},[144,979,980],{"class":154},"\"append\"",[144,982,762],{"class":664},[144,984,920],{"class":887},[144,986,881],{"class":660},[144,988,989],{"class":154},"\"Approved domain; repeatable\"",[144,991,896],{"class":664},[144,993,995,997,1000,1002,1005,1007,1010,1012,1014,1016,1019],{"class":146,"line":994},39,[144,996,902],{"class":664},[144,998,999],{"class":154},"\"--query\"",[144,1001,762],{"class":664},[144,1003,1004],{"class":887},"default",[144,1006,881],{"class":660},[144,1008,1009],{"class":154},"\"\"",[144,1011,762],{"class":664},[144,1013,920],{"class":887},[144,1015,881],{"class":660},[144,1017,1018],{"class":154},"\"Optional Cloud Asset Inventory query string\"",[144,1020,896],{"class":664},[144,1022,1024,1026,1029,1031,1033,1035,1038,1040,1042,1044,1047],{"class":146,"line":1023},40,[144,1025,902],{"class":664},[144,1027,1028],{"class":154},"\"--output\"",[144,1030,762],{"class":664},[144,1032,1004],{"class":887},[144,1034,881],{"class":660},[144,1036,1037],{"class":154},"\"iam-audit.csv\"",[144,1039,762],{"class":664},[144,1041,920],{"class":887},[144,1043,881],{"class":660},[144,1045,1046],{"class":154},"\"Path to the output CSV\"",[144,1048,896],{"class":664},[144,1050,1052,1055],{"class":146,"line":1051},41,[144,1053,1054],{"class":660},"    return",[144,1056,1057],{"class":664}," parser.parse_args()\n",[144,1059,1061],{"class":146,"line":1060},42,[144,1062,192],{"emptyLinePlaceholder":191},[144,1064,1066],{"class":146,"line":1065},43,[144,1067,192],{"emptyLinePlaceholder":191},[144,1069,1071,1073,1076],{"class":146,"line":1070},44,[144,1072,866],{"class":660},[144,1074,1075],{"class":150}," flag_binding",[144,1077,1078],{"class":664},"(member, role, approved_domains):\n",[144,1080,1082],{"class":146,"line":1081},45,[144,1083,1084],{"class":154},"    \"\"\"Returns (category, reason) for a flagged binding, or (None, \"\").\"\"\"\n",[144,1086,1088,1091,1094,1097,1100],{"class":146,"line":1087},46,[144,1089,1090],{"class":660},"    if",[144,1092,1093],{"class":664}," member ",[144,1095,1096],{"class":660},"in",[144,1098,1099],{"class":161}," PUBLIC_MEMBERS",[144,1101,1102],{"class":664},":\n",[144,1104,1106,1109,1112,1114,1117,1120,1123,1126,1129],{"class":146,"line":1105},47,[144,1107,1108],{"class":660},"        return",[144,1110,1111],{"class":154}," \"public\"",[144,1113,762],{"class":664},[144,1115,1116],{"class":660},"f",[144,1118,1119],{"class":154},"\"public binding (",[144,1121,1122],{"class":161},"{",[144,1124,1125],{"class":664},"member",[144,1127,1128],{"class":161},"}",[144,1130,1131],{"class":154},")\"\n",[144,1133,1135],{"class":146,"line":1134},48,[144,1136,192],{"emptyLinePlaceholder":191},[144,1138,1140,1142,1145,1148],{"class":146,"line":1139},49,[144,1141,1090],{"class":660},[144,1143,1144],{"class":664}," member.startswith(",[144,1146,1147],{"class":154},"\"deleted:\"",[144,1149,1150],{"class":664},"):\n",[144,1152,1154,1156,1159,1161],{"class":146,"line":1153},50,[144,1155,1108],{"class":660},[144,1157,1158],{"class":154}," \"deleted\"",[144,1160,762],{"class":664},[144,1162,1163],{"class":154},"\"deleted principal still bound\"\n",[144,1165,1167],{"class":146,"line":1166},51,[144,1168,192],{"emptyLinePlaceholder":191},[144,1170,1172,1175,1177,1179,1182,1185,1188,1190,1193],{"class":146,"line":1171},52,[144,1173,1174],{"class":664},"    is_user_or_group ",[144,1176,881],{"class":660},[144,1178,1144],{"class":664},[144,1180,1181],{"class":154},"\"user:\"",[144,1183,1184],{"class":664},") ",[144,1186,1187],{"class":660},"or",[144,1189,1144],{"class":664},[144,1191,1192],{"class":154},"\"group:\"",[144,1194,896],{"class":664},[144,1196,1198],{"class":146,"line":1197},53,[144,1199,192],{"emptyLinePlaceholder":191},[144,1201,1203,1205,1208,1211,1214,1216,1219],{"class":146,"line":1202},54,[144,1204,1090],{"class":660},[144,1206,1207],{"class":664}," is_user_or_group ",[144,1209,1210],{"class":660},"and",[144,1212,1213],{"class":664}," role ",[144,1215,1096],{"class":660},[144,1217,1218],{"class":161}," BASIC_ROLES",[144,1220,1102],{"class":664},[144,1222,1224,1226,1229,1231,1233,1236,1238,1241,1243],{"class":146,"line":1223},55,[144,1225,1108],{"class":660},[144,1227,1228],{"class":154}," \"basic\"",[144,1230,762],{"class":664},[144,1232,1116],{"class":660},[144,1234,1235],{"class":154},"\"basic role (",[144,1237,1122],{"class":161},[144,1239,1240],{"class":664},"role",[144,1242,1128],{"class":161},[144,1244,1245],{"class":154},") granted to a user or group\"\n",[144,1247,1249],{"class":146,"line":1248},56,[144,1250,192],{"emptyLinePlaceholder":191},[144,1252,1254,1256],{"class":146,"line":1253},57,[144,1255,1090],{"class":660},[144,1257,1258],{"class":664}," is_user_or_group:\n",[144,1260,1262,1265,1267,1270,1273,1275,1278,1281,1283,1286,1289,1291,1294,1296],{"class":146,"line":1261},58,[144,1263,1264],{"class":664},"        domain ",[144,1266,881],{"class":660},[144,1268,1269],{"class":664}," member.split(",[144,1271,1272],{"class":154},"\":\"",[144,1274,762],{"class":664},[144,1276,1277],{"class":161},"1",[144,1279,1280],{"class":664},")[",[144,1282,1277],{"class":161},[144,1284,1285],{"class":664},"].split(",[144,1287,1288],{"class":154},"\"@\"",[144,1290,1280],{"class":664},[144,1292,1293],{"class":660},"-",[144,1295,1277],{"class":161},[144,1297,1298],{"class":664},"].lower()\n",[144,1300,1302,1305,1308,1311,1314],{"class":146,"line":1301},59,[144,1303,1304],{"class":660},"        if",[144,1306,1307],{"class":664}," domain ",[144,1309,1310],{"class":660},"not",[144,1312,1313],{"class":660}," in",[144,1315,1316],{"class":664}," approved_domains:\n",[144,1318,1320,1323,1326,1328,1330,1333,1335,1338,1340],{"class":146,"line":1319},60,[144,1321,1322],{"class":660},"            return",[144,1324,1325],{"class":154}," \"external\"",[144,1327,762],{"class":664},[144,1329,1116],{"class":660},[144,1331,1332],{"class":154},"\"external identity (",[144,1334,1122],{"class":161},[144,1336,1337],{"class":664},"domain",[144,1339,1128],{"class":161},[144,1341,1131],{"class":154},[144,1343,1345],{"class":146,"line":1344},61,[144,1346,192],{"emptyLinePlaceholder":191},[144,1348,1350,1352,1355,1357],{"class":146,"line":1349},62,[144,1351,1054],{"class":660},[144,1353,1354],{"class":161}," None",[144,1356,762],{"class":664},[144,1358,1359],{"class":154},"\"\"\n",[144,1361,1363],{"class":146,"line":1362},63,[144,1364,192],{"emptyLinePlaceholder":191},[144,1366,1368],{"class":146,"line":1367},64,[144,1369,192],{"emptyLinePlaceholder":191},[144,1371,1373,1375,1378],{"class":146,"line":1372},65,[144,1374,866],{"class":660},[144,1376,1377],{"class":150}," main",[144,1379,872],{"class":664},[144,1381,1383,1386,1388],{"class":146,"line":1382},66,[144,1384,1385],{"class":664},"    args ",[144,1387,881],{"class":660},[144,1389,1390],{"class":664}," parse_args()\n",[144,1392,1394,1397,1399,1402,1405,1408,1410],{"class":146,"line":1393},67,[144,1395,1396],{"class":664},"    approved ",[144,1398,881],{"class":660},[144,1400,1401],{"class":664}," {d.lower() ",[144,1403,1404],{"class":660},"for",[144,1406,1407],{"class":664}," d ",[144,1409,1096],{"class":660},[144,1411,1412],{"class":664}," args.domain}\n",[144,1414,1416],{"class":146,"line":1415},68,[144,1417,192],{"emptyLinePlaceholder":191},[144,1419,1421,1424,1426],{"class":146,"line":1420},69,[144,1422,1423],{"class":664},"    client ",[144,1425,881],{"class":660},[144,1427,1428],{"class":664}," asset_v1.AssetServiceClient(\n",[144,1430,1432,1435,1437,1440,1443,1445],{"class":146,"line":1431},70,[144,1433,1434],{"class":887},"        client_options",[144,1436,881],{"class":660},[144,1438,1439],{"class":664},"ClientOptions(",[144,1441,1442],{"class":887},"quota_project_id",[144,1444,881],{"class":660},[144,1446,1447],{"class":664},"args.quota_project)\n",[144,1449,1451],{"class":146,"line":1450},71,[144,1452,1453],{"class":664},"    )\n",[144,1455,1457,1460,1462,1465,1468,1470,1473,1475],{"class":146,"line":1456},72,[144,1458,1459],{"class":664},"    scope ",[144,1461,881],{"class":660},[144,1463,1464],{"class":660}," f",[144,1466,1467],{"class":154},"\"organizations\u002F",[144,1469,1122],{"class":161},[144,1471,1472],{"class":664},"args.organization",[144,1474,1128],{"class":161},[144,1476,1477],{"class":154},"\"\n",[144,1479,1481,1484,1486,1489,1492,1494,1497,1500,1502,1505,1508,1510,1513],{"class":146,"line":1480},73,[144,1482,1483],{"class":664},"    request ",[144,1485,881],{"class":660},[144,1487,1488],{"class":664}," asset_v1.SearchAllIamPoliciesRequest(",[144,1490,1491],{"class":887},"scope",[144,1493,881],{"class":660},[144,1495,1496],{"class":664},"scope, ",[144,1498,1499],{"class":887},"query",[144,1501,881],{"class":660},[144,1503,1504],{"class":664},"args.query, ",[144,1506,1507],{"class":887},"page_size",[144,1509,881],{"class":660},[144,1511,1512],{"class":161},"500",[144,1514,896],{"class":664},[144,1516,1518],{"class":146,"line":1517},74,[144,1519,192],{"emptyLinePlaceholder":191},[144,1521,1523,1526,1529,1531,1534,1536,1538,1540,1543],{"class":146,"line":1522},75,[144,1524,1525],{"class":161},"    print",[144,1527,1528],{"class":664},"(",[144,1530,1116],{"class":660},[144,1532,1533],{"class":154},"\"Searching ",[144,1535,1122],{"class":161},[144,1537,1491],{"class":664},[144,1539,1128],{"class":161},[144,1541,1542],{"class":154}," for IAM allow policies ...\"",[144,1544,896],{"class":664},[144,1546,1548],{"class":146,"line":1547},76,[144,1549,192],{"emptyLinePlaceholder":191},[144,1551,1553,1556,1558],{"class":146,"line":1552},77,[144,1554,1555],{"class":664},"    rows ",[144,1557,881],{"class":660},[144,1559,1560],{"class":664}," []\n",[144,1562,1564,1567,1569],{"class":146,"line":1563},78,[144,1565,1566],{"class":664},"    counts ",[144,1568,881],{"class":660},[144,1570,1571],{"class":664}," Counter()\n",[144,1573,1575,1578,1580],{"class":146,"line":1574},79,[144,1576,1577],{"class":664},"    resources ",[144,1579,881],{"class":660},[144,1581,1582],{"class":161}," 0\n",[144,1584,1586],{"class":146,"line":1585},80,[144,1587,192],{"emptyLinePlaceholder":191},[144,1589,1591,1594],{"class":146,"line":1590},81,[144,1592,1593],{"class":660},"    try",[144,1595,1102],{"class":664},[144,1597,1599,1602,1605,1607,1610,1613,1615],{"class":146,"line":1598},82,[144,1600,1601],{"class":660},"        for",[144,1603,1604],{"class":664}," result ",[144,1606,1096],{"class":660},[144,1608,1609],{"class":664}," client.search_all_iam_policies(",[144,1611,1612],{"class":887},"request",[144,1614,881],{"class":660},[144,1616,1617],{"class":664},"request):\n",[144,1619,1621,1624,1627],{"class":146,"line":1620},83,[144,1622,1623],{"class":664},"            resources ",[144,1625,1626],{"class":660},"+=",[144,1628,1629],{"class":161}," 1\n",[144,1631,1633,1636,1639,1641],{"class":146,"line":1632},84,[144,1634,1635],{"class":660},"            for",[144,1637,1638],{"class":664}," binding ",[144,1640,1096],{"class":660},[144,1642,1643],{"class":664}," result.policy.bindings:\n",[144,1645,1647,1650,1652,1655,1658,1661,1664],{"class":146,"line":1646},85,[144,1648,1649],{"class":664},"                condition ",[144,1651,881],{"class":660},[144,1653,1654],{"class":664}," binding.condition.title ",[144,1656,1657],{"class":660},"if",[144,1659,1660],{"class":664}," binding.condition.expression ",[144,1662,1663],{"class":660},"else",[144,1665,1666],{"class":154}," \"\"\n",[144,1668,1670,1673,1675,1677],{"class":146,"line":1669},86,[144,1671,1672],{"class":660},"                for",[144,1674,1093],{"class":664},[144,1676,1096],{"class":660},[144,1678,1679],{"class":664}," binding.members:\n",[144,1681,1683,1686,1688],{"class":146,"line":1682},87,[144,1684,1685],{"class":664},"                    category, reason ",[144,1687,881],{"class":660},[144,1689,1690],{"class":664}," flag_binding(member, binding.role, approved)\n",[144,1692,1694,1697],{"class":146,"line":1693},88,[144,1695,1696],{"class":660},"                    if",[144,1698,1699],{"class":664}," category:\n",[144,1701,1703,1706,1708],{"class":146,"line":1702},89,[144,1704,1705],{"class":664},"                        counts[category] ",[144,1707,1626],{"class":660},[144,1709,1629],{"class":161},[144,1711,1713],{"class":146,"line":1712},90,[144,1714,1715],{"class":664},"                    rows.append(\n",[144,1717,1719],{"class":146,"line":1718},91,[144,1720,1721],{"class":664},"                        {\n",[144,1723,1725,1728],{"class":146,"line":1724},92,[144,1726,1727],{"class":154},"                            \"resource\"",[144,1729,1730],{"class":664},": result.resource,\n",[144,1732,1734,1737],{"class":146,"line":1733},93,[144,1735,1736],{"class":154},"                            \"asset_type\"",[144,1738,1739],{"class":664},": result.asset_type,\n",[144,1741,1743,1746],{"class":146,"line":1742},94,[144,1744,1745],{"class":154},"                            \"project\"",[144,1747,1748],{"class":664},": result.project,\n",[144,1750,1752,1755],{"class":146,"line":1751},95,[144,1753,1754],{"class":154},"                            \"role\"",[144,1756,1757],{"class":664},": binding.role,\n",[144,1759,1761,1764],{"class":146,"line":1760},96,[144,1762,1763],{"class":154},"                            \"member\"",[144,1765,1766],{"class":664},": member,\n",[144,1768,1770,1773],{"class":146,"line":1769},97,[144,1771,1772],{"class":154},"                            \"condition\"",[144,1774,1775],{"class":664},": condition,\n",[144,1777,1779,1782,1785,1788],{"class":146,"line":1778},98,[144,1780,1781],{"class":154},"                            \"flagged\"",[144,1783,1784],{"class":664},": ",[144,1786,1787],{"class":161},"bool",[144,1789,1790],{"class":664},"(category),\n",[144,1792,1794,1797],{"class":146,"line":1793},99,[144,1795,1796],{"class":154},"                            \"flag_reason\"",[144,1798,1799],{"class":664},": reason,\n",[144,1801,1803],{"class":146,"line":1802},100,[144,1804,1805],{"class":664},"                        }\n",[144,1807,1809],{"class":146,"line":1808},101,[144,1810,1811],{"class":664},"                    )\n",[144,1813,1815,1818,1821,1824],{"class":146,"line":1814},102,[144,1816,1817],{"class":660},"    except",[144,1819,1820],{"class":664}," GoogleAPICallError ",[144,1822,1823],{"class":660},"as",[144,1825,1826],{"class":664}," exc:\n",[144,1828,1830,1833,1835,1837,1840,1842,1845,1847,1850,1852,1855,1857],{"class":146,"line":1829},103,[144,1831,1832],{"class":161},"        print",[144,1834,1528],{"class":664},[144,1836,1116],{"class":660},[144,1838,1839],{"class":154},"\"Cloud Asset Inventory call failed: ",[144,1841,1122],{"class":161},[144,1843,1844],{"class":664},"exc",[144,1846,1128],{"class":161},[144,1848,1849],{"class":154},"\"",[144,1851,762],{"class":664},[144,1853,1854],{"class":887},"file",[144,1856,881],{"class":660},[144,1858,1859],{"class":664},"sys.stderr)\n",[144,1861,1863,1866,1868],{"class":146,"line":1862},104,[144,1864,1865],{"class":664},"        sys.exit(",[144,1867,1277],{"class":161},[144,1869,896],{"class":664},[144,1871,1873],{"class":146,"line":1872},105,[144,1874,192],{"emptyLinePlaceholder":191},[144,1876,1878,1880,1882,1884,1887,1890,1893,1896,1898,1901,1903,1906,1908,1910,1913],{"class":146,"line":1877},106,[144,1879,1525],{"class":161},[144,1881,1528],{"class":664},[144,1883,1116],{"class":660},[144,1885,1886],{"class":154},"\"Retrieved ",[144,1888,1889],{"class":161},"{len",[144,1891,1892],{"class":664},"(rows)",[144,1894,1895],{"class":660},":,",[144,1897,1128],{"class":161},[144,1899,1900],{"class":154}," bindings across ",[144,1902,1122],{"class":161},[144,1904,1905],{"class":664},"resources",[144,1907,1895],{"class":660},[144,1909,1128],{"class":161},[144,1911,1912],{"class":154}," resources.\"",[144,1914,896],{"class":664},[144,1916,1918,1920,1922,1924,1927,1929,1932,1935,1938,1940,1942],{"class":146,"line":1917},107,[144,1919,1525],{"class":161},[144,1921,1528],{"class":664},[144,1923,1116],{"class":660},[144,1925,1926],{"class":154},"\"  basic role to user\u002Fgroup ........ ",[144,1928,1122],{"class":161},[144,1930,1931],{"class":664},"counts[",[144,1933,1934],{"class":154},"'basic'",[144,1936,1937],{"class":664},"]",[144,1939,1128],{"class":161},[144,1941,1849],{"class":154},[144,1943,896],{"class":664},[144,1945,1947,1949,1951,1953,1956,1958,1960,1963,1965,1967,1969],{"class":146,"line":1946},108,[144,1948,1525],{"class":161},[144,1950,1528],{"class":664},[144,1952,1116],{"class":660},[144,1954,1955],{"class":154},"\"  public (allUsers\u002FallAuth) ....... ",[144,1957,1122],{"class":161},[144,1959,1931],{"class":664},[144,1961,1962],{"class":154},"'public'",[144,1964,1937],{"class":664},[144,1966,1128],{"class":161},[144,1968,1849],{"class":154},[144,1970,896],{"class":664},[144,1972,1974,1976,1978,1980,1983,1985,1987,1990,1992,1994,1996],{"class":146,"line":1973},109,[144,1975,1525],{"class":161},[144,1977,1528],{"class":664},[144,1979,1116],{"class":660},[144,1981,1982],{"class":154},"\"  external identity ............... ",[144,1984,1122],{"class":161},[144,1986,1931],{"class":664},[144,1988,1989],{"class":154},"'external'",[144,1991,1937],{"class":664},[144,1993,1128],{"class":161},[144,1995,1849],{"class":154},[144,1997,896],{"class":664},[144,1999,2001,2003,2005,2007,2010,2012,2014,2017,2019,2021,2023],{"class":146,"line":2000},110,[144,2002,1525],{"class":161},[144,2004,1528],{"class":664},[144,2006,1116],{"class":660},[144,2008,2009],{"class":154},"\"  deleted principal ............... ",[144,2011,1122],{"class":161},[144,2013,1931],{"class":664},[144,2015,2016],{"class":154},"'deleted'",[144,2018,1937],{"class":664},[144,2020,1128],{"class":161},[144,2022,1849],{"class":154},[144,2024,896],{"class":664},[144,2026,2028],{"class":146,"line":2027},111,[144,2029,192],{"emptyLinePlaceholder":191},[144,2031,2033,2036,2039,2042,2045,2047,2050,2052,2054,2056,2059,2061,2064,2066,2068],{"class":146,"line":2032},112,[144,2034,2035],{"class":660},"    with",[144,2037,2038],{"class":161}," open",[144,2040,2041],{"class":664},"(args.output, ",[144,2043,2044],{"class":154},"\"w\"",[144,2046,762],{"class":664},[144,2048,2049],{"class":887},"newline",[144,2051,881],{"class":660},[144,2053,1009],{"class":154},[144,2055,762],{"class":664},[144,2057,2058],{"class":887},"encoding",[144,2060,881],{"class":660},[144,2062,2063],{"class":154},"\"utf-8\"",[144,2065,1184],{"class":664},[144,2067,1823],{"class":660},[144,2069,2070],{"class":664}," csv_file:\n",[144,2072,2074,2077,2079,2082,2085,2087,2089],{"class":146,"line":2073},113,[144,2075,2076],{"class":664},"        writer ",[144,2078,881],{"class":660},[144,2080,2081],{"class":664}," csv.DictWriter(csv_file, ",[144,2083,2084],{"class":887},"fieldnames",[144,2086,881],{"class":660},[144,2088,804],{"class":161},[144,2090,896],{"class":664},[144,2092,2094],{"class":146,"line":2093},114,[144,2095,2096],{"class":664},"        writer.writeheader()\n",[144,2098,2100],{"class":146,"line":2099},115,[144,2101,2102],{"class":664},"        writer.writerows(rows)\n",[144,2104,2106],{"class":146,"line":2105},116,[144,2107,192],{"emptyLinePlaceholder":191},[144,2109,2111,2113,2115,2117,2120,2122,2125,2127,2129],{"class":146,"line":2110},117,[144,2112,1525],{"class":161},[144,2114,1528],{"class":664},[144,2116,1116],{"class":660},[144,2118,2119],{"class":154},"\"Report written to ",[144,2121,1122],{"class":161},[144,2123,2124],{"class":664},"args.output",[144,2126,1128],{"class":161},[144,2128,1849],{"class":154},[144,2130,896],{"class":664},[144,2132,2134],{"class":146,"line":2133},118,[144,2135,192],{"emptyLinePlaceholder":191},[144,2137,2139],{"class":146,"line":2138},119,[144,2140,192],{"emptyLinePlaceholder":191},[144,2142,2144,2146,2149,2152,2155],{"class":146,"line":2143},120,[144,2145,1657],{"class":660},[144,2147,2148],{"class":161}," __name__",[144,2150,2151],{"class":660}," ==",[144,2153,2154],{"class":154}," \"__main__\"",[144,2156,1102],{"class":664},[144,2158,2160],{"class":146,"line":2159},121,[144,2161,2162],{"class":664},"    main()\n",[65,2164,2166],{"id":2165},"notes","Notes",[24,2168,2169,2183,2186,2189,2198,2222,2235,2246],{},[27,2170,2171,2173,2174,2178,2179,2182],{},[15,2172,21],{}," returns the allow policy ",[2175,2176,2177],"em",{},"attached"," to each resource, not effective access. The docs spell this out: a search on a project returns principals granted roles on that project and doesn't include principals who inherit access from a folder or the organization. So a binding at the org node shows up once, as a row on the organization resource, not on every project beneath it. To answer \"who can actually do X on this resource,\" including inherited and group-expanded access, use Policy Analyzer: ",[15,2180,2181],{},"gcloud asset analyze-iam-policy --organization=123456789012 --full-resource-name=\u002F\u002Fcloudresourcemanager.googleapis.com\u002Fprojects\u002Fweb-prod --permissions=resourcemanager.projects.setIamPolicy --expand-groups",". Policy Analyzer is quota-limited: more than 20 queries per organization per day needs Security Command Center Premium or Enterprise.",[27,2184,2185],{},"Coverage is limited to searchable asset types: Google notes that not every resource type is available in the search APIs, so check the Cloud Asset Inventory resource types list before treating the report as complete for a given service.",[27,2187,2188],{},"The results cover allow policies only. IAM deny policies, principal access boundaries, and organization policies can all make a flagged binding less dangerous than it looks, or a clean one more.",[27,2190,2191,2193,2194,2197],{},[15,2192,1507],{}," tops out at 500. The client's pager follows ",[15,2195,2196],{},"next_page_token"," for you, so the loop above walks every page without extra code.",[27,2199,2200,2201,2204,2205,50,2207,2209,2210,33,2212,2214,2215,33,2218,2221],{},"Conditional bindings are kept, with the condition's title in the ",[15,2202,2203],{},"condition"," column. The legacy basic roles can't carry a condition (IAM rejects conditions on Owner, Editor, and Viewer), so a flagged ",[15,2206,32],{},[15,2208,17],{}," row is always unconditional; ",[15,2211,39],{},[15,2213,42],{}," can be conditional, so check that column for them. ",[15,2216,2217],{},"roles\u002Freader",[15,2219,2220],{},"roles\u002Fviewer"," are basic roles too, but read-only, so the script leaves them unflagged.",[27,2223,2224,2225,2227,2228,2231,2232,122],{},"Service accounts are deliberately not domain-checked. ",[15,2226,132],{}," members legitimately live in other projects, including Google-managed service agents (",[15,2229,2230],{},"service-PROJECT_NUMBER@gcp-sa-*.iam.gserviceaccount.com","). If you want to catch service accounts from projects outside your org, compare the project part of the address with a list from ",[15,2233,2234],{},"gcloud projects list",[27,2236,2237,2238,2241,2242,2245],{},"Group grants are reported as the group, not its members. An external user nested inside an internal group won't be flagged here; ",[15,2239,2240],{},"--expand-groups"," on Policy Analyzer, or a Cloud Identity group membership export, is how you close that gap. Expanding Google Workspace groups in Policy Analyzer needs the Workspace ",[15,2243,2244],{},"groups.read"," permission (in the Groups Reader admin role) on top of the Cloud IAM roles above.",[27,2247,2248,2249,2251],{},"I diff each run's CSV against the previous one instead of reading the whole report fresh. A new basic-role grant or a new ",[15,2250,49],{}," row deserves an immediate look; a binding that hasn't changed since last month usually doesn't.",[65,2253,2255],{"id":2254},"references","References",[24,2257,2258,2267,2274,2281,2288,2295,2302,2309],{},[27,2259,2260],{},[2261,2262,2266],"a",{"href":2263,"rel":2264},"https:\u002F\u002Fdocs.cloud.google.com\u002Fasset-inventory\u002Fdocs\u002Fsearching-iam-policies",[2265],"nofollow","Search IAM allow policies (Cloud Asset Inventory)",[27,2268,2269],{},[2261,2270,2273],{"href":2271,"rel":2272},"https:\u002F\u002Fdocs.cloud.google.com\u002Fasset-inventory\u002Fdocs\u002Faccess-control",[2265],"Cloud Asset Inventory access control",[27,2275,2276],{},[2261,2277,2280],{"href":2278,"rel":2279},"https:\u002F\u002Fdocs.cloud.google.com\u002Fpython\u002Fdocs\u002Freference\u002Fcloudasset\u002Flatest\u002Fgoogle.cloud.asset_v1.services.asset_service.AssetServiceClient",[2265],"AssetServiceClient reference (Python)",[27,2282,2283],{},[2261,2284,2287],{"href":2285,"rel":2286},"https:\u002F\u002Fdocs.cloud.google.com\u002Fasset-inventory\u002Fdocs\u002Fquery-syntax",[2265],"Cloud Asset Inventory query syntax",[27,2289,2290],{},[2261,2291,2294],{"href":2292,"rel":2293},"https:\u002F\u002Fgoogleapis.dev\u002Fpython\u002Fgoogle-api-core\u002Flatest\u002Fclient_options.html",[2265],"google-api-core ClientOptions",[27,2296,2297],{},[2261,2298,2301],{"href":2299,"rel":2300},"https:\u002F\u002Fdocs.cloud.google.com\u002Fpolicy-intelligence\u002Fdocs\u002Fanalyze-iam-policies",[2265],"Analyze IAM policies (Policy Analyzer)",[27,2303,2304],{},[2261,2305,2308],{"href":2306,"rel":2307},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Froles-overview",[2265],"Roles and permissions overview (basic role guidance)",[27,2310,2311],{},[2261,2312,2315],{"href":2313,"rel":2314},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Fconfiguring-temporary-access",[2265],"Configure temporary access (conditions and basic roles)",[2317,2318,2319],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":140,"searchDepth":168,"depth":168,"links":2321},[2322,2323,2324,2325,2326,2327],{"id":67,"depth":168,"text":68},{"id":227,"depth":168,"text":228},{"id":333,"depth":168,"text":334},{"id":549,"depth":168,"text":550},{"id":2165,"depth":168,"text":2166},{"id":2254,"depth":168,"text":2255},"techcolumnist",[2330,2331],"scripts","engineering","2026-01-14T14:00:00Z","A Python script that pulls the IAM bindings across a GCP organization through Cloud Asset Inventory and flags basic roles, public access, and external identities.","md",false,null,{},"\u002Fblog\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization",{"title":6,"description":2333},[2328],"blog\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization",[447,151],"\u002F2026\u002F01\u002F14\u002Fpython-gcloud-audit-iam-bindings-across-a-gcp-organization\u002F","u-2jiatZXDteY6qil8UVjjzw7rXkywJhhtnhnhDXVLU",{"title":2346,"description":2347,"date":2348,"url":2349,"categories":2350,"tags":2351,"image":2336,"readingTime":179,"canonical":2328,"sites":2355,"series":2336,"seriesOrder":2336},"Python: Data Warehouse – Catching Schema Drift Before an ETL Run","A Python pre-flight check that snapshots Postgres column definitions from information_schema and fails the ETL run when a column is dropped, retyped, or resized.","2026-01-21T14:00:00Z","\u002F2026\u002F01\u002F21\u002Fpython-data-warehouse-catching-schema-drift-before-an-etl-run\u002F",[2330,2331],[447,2352,2353,2354],"data-warehouse","etl","sql",[2328],{"title":2357,"description":2358,"date":2359,"url":2360,"categories":2361,"tags":2362,"image":2336,"readingTime":179,"canonical":2328,"sites":2365,"series":2336,"seriesOrder":2336},"Python: Discovery – Fingerprint Every Device on a Subnet","A Python script that ARP-sweeps a local subnet with Scapy, maps MACs to IEEE vendors, probes common ports for banners, and writes one CSV row per device.","2026-01-07T14:00:00Z","\u002F2026\u002F01\u002F07\u002Fpython-discovery-fingerprint-every-device-on-a-subnet\u002F",[2330,2331],[447,2363,2364],"discovery","network",[2328],[2367,2375,2384],{"title":2368,"description":2369,"date":2370,"url":2371,"categories":2372,"tags":2373,"image":2336,"readingTime":195,"canonical":2328,"sites":2374,"series":2336,"seriesOrder":2336},"Python: Data Warehouse – Incremental Loads from Postgres to BigQuery","A Python script that copies new and changed Postgres rows into BigQuery with one load job and a MERGE, advancing the watermark in the same transaction.","2025-12-17T14:00:00Z","\u002F2025\u002F12\u002F17\u002Fpython-data-warehouse-incremental-loads-from-postgres-to-bigquery\u002F",[2330,2331],[447,2352,2353,151,2354],[2328],{"title":2376,"description":2377,"date":2378,"url":2379,"categories":2380,"tags":2381,"image":2336,"readingTime":188,"canonical":2328,"sites":2383,"series":2336,"seriesOrder":2336},"Node.js: gcloud – Rotating Service Account Keys Before They Expire","A Node.js script that finds aging Google Cloud service account keys, mints one replacement, and disables the old keys once it is deployed.","2026-03-04T14:00:00Z","\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire\u002F",[2330,2331],[2382,151],"nodejs",[2328],{"title":2385,"description":2386,"date":2387,"url":2388,"categories":2389,"tags":2390,"image":2336,"readingTime":195,"canonical":2328,"sites":2393,"series":2336,"seriesOrder":2336},"Python: Talos – Health-Checking a Kubernetes Cluster from a Cron Job","A cron-friendly Python health check for Talos clusters: talosctl health, etcd alarms, node readiness, cordons and crash-looping pods, with a webhook alert.","2026-02-04T14:00:00Z","\u002F2026\u002F02\u002F04\u002Fpython-talos-health-checking-a-kubernetes-cluster-from-a-cron-job\u002F",[2330,2331],[447,2391,2392],"talos","kubernetes",[2328],{"doc":2336,"posts":2395},[],1790052513406]