Control: Compute image IAM policies should only grant access to trusted principals
Description
This control checks whether Compute image IAM policies grant access to untrusted users, groups, domains, or service accounts.
Usage
Run the control in your terminal:
powerpipe control run gcp_perimeter.control.compute_image_policy_shared_access
Snapshot and share results via Turbot Pipes:
powerpipe loginpowerpipe control run gcp_perimeter.control.compute_image_policy_shared_access --share
Steampipe Tables
SQL
with policy_analysis as ( select name as resource_id, -- Count all members count(*) as total_members, -- Count project-level members count(*) filter (where member like 'project%') as project_members, -- Count trusted members (excluding project-level) count(*) filter (where member not like 'project%' and ( (member like 'user:%' and split_part(member, 'user:', 2) = any(($1)::text[])) or (member like 'group:%' and split_part(member, 'group:', 2) = any(($2)::text[])) or (member like 'domain:%' and split_part(member, 'domain:', 2) = any(($3)::text[])) or (member like 'serviceAccount:%' and split_part(member, 'serviceAccount:', 2) = any(($4)::text[])) ) ) as trusted_members, -- Collect untrusted members for alarm messages array_agg(distinct member) filter (where member not like 'project%' and not ( (member like 'user:%' and split_part(member, 'user:', 2) = any(($1)::text[])) or (member like 'group:%' and split_part(member, 'group:', 2) = any(($2)::text[])) or (member like 'domain:%' and split_part(member, 'domain:', 2) = any(($3)::text[])) or (member like 'serviceAccount:%' and split_part(member, 'serviceAccount:', 2) = any(($4)::text[])) ) ) as untrusted_members from gcp_compute_image, jsonb_array_elements(iam_policy -> 'bindings') as binding, jsonb_array_elements_text(binding -> 'members') as member group by name ) select r.name as resource, case -- SKIP: When no members exist when (r.iam_policy -> 'bindings') is null or jsonb_array_length(r.iam_policy -> 'bindings') = 0 then 'skip' -- INFO: When only project-level roles are assigned when p.total_members = p.project_members then 'info' -- OK: When all non-project members are trusted when p.untrusted_members is null and (p.trusted_members > 0 or p.project_members > 0) then 'ok' -- ALARM: When there are untrusted members else 'alarm' end as status, case when (r.iam_policy -> 'bindings') is null or jsonb_array_length(r.iam_policy -> 'bindings') = 0 then title || ' has no IAM policy members.' when p.total_members = p.project_members then title || ' only has project-level role assignments (' || p.project_members || ' members).' when p.untrusted_members is null and (p.trusted_members > 0 or p.project_members > 0) then title || ' policy only grants access to trusted principals (' || coalesce(p.trusted_members, 0) || ' trusted + ' || coalesce(p.project_members, 0) || ' project-level).' else title || ' policy contains ' || coalesce(array_length(p.untrusted_members, 1), 0) || ' untrusted member(s): ' || array_to_string(p.untrusted_members, ', ') end as reason, r.project, r.location from gcp_compute_image as r left join policy_analysis as p on p.resource_id = r.name where -- Only check resources where we have access to IAM policy r.iam_policy is not null;
Params
Args | Name | Default | Description | Variable |
---|---|---|---|---|
$1 | trusted_users |
| A list of trusted Google Account emails. | |
$2 | trusted_groups |
| A list of trusted Google Groups. | |
$3 | trusted_domains |
| A list of trusted Google Workspace domains. | |
$4 | trusted_service_accounts |
| A list of trusted service accounts. |