Cloud Account Configuration¶
Before running an inspection you need to configure credentials so VirtualSutra AI can read your cloud resources. All access is read-only - the platform never creates, modifies, or deletes any of your resources.
AWS¶
What you need¶
| Field | Description |
|---|---|
| Account ID | 12-digit AWS account number (e.g. 123456789012) |
| Role ARN | The IAM role in your account that VirtualSutra will assume |
| External ID | On the hosted service, generated by the platform for your organization (see below). Self-hosted, a value you choose and enter on the connection |
| Regions | One or more AWS regions to scan (e.g. us-east-1, eu-west-1) |
| Credentials | Optional - access key / secret key, or omit to use the ambient role chain |
Creating the inspection IAM role¶
Create a cross-account IAM role in your AWS account that trusts the VirtualSutra AI inspection account. Ask your administrator for the trusted account ID.
Step 1 - Find your External ID
The trust policy should include an sts:ExternalId condition. The External ID stops another organization from getting the platform to assume your role (the "confused deputy" problem).
- Hosted service: the platform generates one External ID for your organization, a value starting with
vsutra-. You cannot choose or change it. Find it under Settings > Connections > Cloud: click Add Cloud Connection and choose the AWS tab. Copy the value, or copy the whole trust-policy condition. You can also read it from the API:GET /orgs/{org_id}/aws-external-idreturns{"external_id": "...", "enforced": true}. The platform presents this value every time it assumes a role for your organization: when it tests a connection and during every scan. Submitting a run, or creating or editing a schedule, whose configuration names any otherexternal_idis refused, so leave the field out. - Self-hosted: choose your own value. Put it in the trust policy and in the External ID field of the connection. The platform presents it as entered, so this case is unchanged.
Step 2 - Create the role with a trust policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<VSUTRA_ACCOUNT_ID>:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "<YOUR_EXTERNAL_ID>"
}
}
}
]
}
Replace <YOUR_EXTERNAL_ID> with the value from Step 1. On the hosted service that is your organization's vsutra-... value, exactly as shown.
Step 3 - Attach managed policies
| Policy ARN | Purpose |
|---|---|
arn:aws:iam::aws:policy/SecurityAudit | IAM, CloudTrail, VPC, S3, certificates |
arn:aws:iam::aws:policy/ReadOnlyAccess | All remaining read APIs |
Step 4 - Record the Role ARN
The Role ARN looks like: arn:aws:iam::123456789012:role/VirtualSutraInspection
Paste this into the Role ARN field in the inspection wizard.
Using the CLI
# Create the role (replace placeholders)
aws iam create-role \
--role-name VirtualSutraInspection \
--assume-role-policy-document file://trust-policy.json
# Attach policies
aws iam attach-role-policy \
--role-name VirtualSutraInspection \
--policy-arn arn:aws:iam::aws:policy/SecurityAudit
aws iam attach-role-policy \
--role-name VirtualSutraInspection \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
Upgrading an existing role on the hosted service
The hosted service used to present the External ID set on each connection or run. It now always presents your organization's own value. If your role's trust policy names a different External ID, the role can no longer be assumed: connection tests and AWS scans fail with an access-denied error. A trust policy with no sts:ExternalId condition keeps working, but any organization could get the platform to assume that role, so add the condition. To fix either case:
- Copy your organization's External ID from Settings > Connections > Cloud (AWS tab).
- Set (or add) the role's
sts:ExternalIdcondition with that value:aws iam update-assume-role-policy --role-name VirtualSutraInspection --policy-document file://trust-policy.json. - Remove
external_idfrom saved run configurations and schedules. Existing schedules keep running with the platform's value, but editing a schedule that still names a differentexternal_idis refused until you remove the field.
Saving an AWS connection from the console removes any External ID stored on it. The platform ignores a stored value either way.
Self-hosted deployments are not affected.
Scanning an AWS Organization: the member role¶
To scan every account of an AWS Organization with one connection, VirtualSutra assumes your hub role and then a member role in each other account. The member role is a read-only role that a CloudFormation StackSet creates in all of them at once. It holds the AWS managed SecurityAudit policy and a handful of reads (the regions, EKS clusters and ECR repositories) and the three ECR actions a scan needs to pull an image; nothing that writes. Its trust policy lets only your hub role assume it, and only with your External ID.
- Download the template: virtualsutra-readonly-stackset.yaml.
- Create the StackSet in the management account, service-managed, with automatic deployment on so accounts added later get the role:
aws cloudformation create-stack-set --stack-set-name VirtualSutraReadOnly \
--template-body file://virtualsutra-readonly-stackset.yaml --permission-model 'SERVICE_MANAGED' \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false \
--capabilities 'CAPABILITY_NAMED_IAM' \
--parameters ParameterKey=HubAccountId,ParameterValue=HUB_ACCOUNT_ID \
ParameterKey=HubRoleName,ParameterValue=HUB_ROLE_NAME ParameterKey=ExternalId,ParameterValue=EXTERNAL_ID
aws cloudformation create-stack-instances --stack-set-name VirtualSutraReadOnly \
--deployment-targets OrganizationalUnitIds=ORGANIZATION_ROOT_ID --regions us-east-1
The role is global, so one region is enough. The management account is skipped by a service-managed StackSet: it is scanned with the hub role itself when the hub role lives there. When the hub role is in a delegated account instead, deploy the same template once in the management account as an ordinary stack.
- The hub role needs to list the Organization and assume the member role. The Check result prints the exact policy and the command that attaches it (
aws iam put-role-policy ... VirtualSutraDiscovery), together withSecurityAuditand the scan's own actions.
The commands the console shows when a member account lacks the role are these same commands with your names filled in. If the connection's Member role name is not VirtualSutraReadOnly, they add ParameterKey=RoleName,ParameterValue=<your name> so the template creates the role under that name; add it yourself when you run the commands above.
Scanning an EKS or GKE cluster: the read-only ClusterRole¶
A scan needs permission to list a few kinds of object inside a cluster, and nothing else. vsutra-readonly-clusterrole.yaml holds a ClusterRole that grants exactly those (no Secrets, no ConfigMaps, nothing that writes) and a binding.
- EKS: create an access entry that maps the scan's IAM role to the group
vsutra-readonly, then apply the file:
aws eks create-access-entry --cluster-name <CLUSTER_NAME> --principal-arn <ROLE_ARN> --kubernetes-groups vsutra-readonly
kubectl apply -f vsutra-readonly-clusterrole.yaml
The cluster's authentication mode must include the API (API or API_AND_CONFIG_MAP). If the cluster's public endpoint is restricted to some networks, add the scanner's address to them; a private-only endpoint cannot be scanned yet.
- GKE: bind the same ClusterRole to the scanning service account instead: edit the binding's subject to the commented
Userentry in the file, with the service account's e-mail address.
Azure¶
What you need¶
| Field | Description |
|---|---|
| Subscription ID | GUID of the Azure subscription to scan |
| Tenant ID | Your Azure AD / Entra ID tenant GUID |
| Client ID | The application (client) ID of the service principal |
| Client Secret | A client secret created for the service principal |
| Regions | Azure regions to include in the scan |
Creating the service principal¶
Step 1 - Create a service principal
az ad sp create-for-rbac --name "vsutra-ai-inspector" --skip-assignment
# Save the appId (Client ID) and password (Client Secret) from the output
Step 2 - Assign required roles
SUBSCRIPTION="/subscriptions/<subscription-id>"
CLIENT_ID="<appId from step 1>"
az role assignment create --assignee "$CLIENT_ID" \
--role "Reader" --scope "$SUBSCRIPTION"
az role assignment create --assignee "$CLIENT_ID" \
--role "Security Reader" --scope "$SUBSCRIPTION"
az role assignment create --assignee "$CLIENT_ID" \
--role "Billing Reader" --scope "$SUBSCRIPTION"
Step 3 - Find your Tenant ID
Roles for connection discovery¶
To let Check list your subscriptions, AKS clusters and ACR repositories (see Connections), assign the principal these roles at each subscription (or at a management group above them):
SUBSCRIPTION="/subscriptions/<subscription-id>"
CLIENT_ID="<appId>"
# Subscriptions, AKS clusters and ACR registries
az role assignment create --assignee "$CLIENT_ID" --role "Reader" --scope "$SUBSCRIPTION"
# The cluster's certificate authority (listClusterUserCredential)
az role assignment create --assignee "$CLIENT_ID" --role "Azure Kubernetes Service Cluster User Role" --scope "$SUBSCRIPTION"
# Repositories and images of each registry
az role assignment create --assignee "$CLIENT_ID" --role "Container Registry Repository Catalog Lister" --scope "$SUBSCRIPTION"
az role assignment create --assignee "$CLIENT_ID" --role "Container Registry Repository Reader" --scope "$SUBSCRIPTION"
The AKS cluster must be integrated with Microsoft Entra. Inside the cluster, bind the read-only ClusterRole to the principal's object ID:
Check prints these commands with your values filled in, for each role it found missing, at subscription scope: for the subscription listing it prints Reader and Security Reader, then the AKS and registry roles above as they are found missing. When the principal sees no subscription at all, Check also prints a Reader assignment at a management group (with a placeholder for the management group id); in every other case it prints no management-group command. To have subscriptions added later followed, assign Reader at a management group (or the tenant root group) above them yourself:
az role assignment create --assignee "$CLIENT_ID" --role "Reader" \
--scope /providers/Microsoft.Management/managementGroups/<management-group-id>
Registry roles. The two registry roles above, Container Registry Repository Catalog Lister and Repository Reader, are those of a registry with repository-scoped permissions (ABAC) enabled. A registry in the default mode needs AcrPull for the pull (see Azure ACR); we could not verify which role lets Check list the repositories of such a registry. If Check still reports it as refused, add the two roles anyway, or save the repository by hand.
What to enter in the wizard¶
- Subscription ID - from
az account show --query id -o tsv - Tenant ID - from step 3
- Client ID -
appIdfrom the service principal - Client Secret -
passwordfrom the service principal
GCP¶
What you need¶
| Field | Description |
|---|---|
| Project ID | The GCP project ID to scan |
| Service Account Key | The JSON key file for a service account in that project |
The steps below set up one service account for one project. To scan many projects with one key, see One service account for many projects.
Creating the service account¶
Step 1 - Create a service account
PROJECT="my-gcp-project"
SA_NAME="vsutra-ai-inspector"
gcloud iam service-accounts create "$SA_NAME" \
--project="$PROJECT" \
--display-name="VirtualSutra AI Inspector"
SA_EMAIL="${SA_NAME}@${PROJECT}.iam.gserviceaccount.com"
Step 2 - Grant required roles
for ROLE in \
roles/viewer \
roles/iam.securityReviewer \
roles/cloudasset.viewer \
roles/logging.viewer \
roles/monitoring.viewer; do
gcloud projects add-iam-policy-binding "$PROJECT" \
--member="serviceAccount:$SA_EMAIL" \
--role="$ROLE" --condition=None
done
These roles cover the base checks. Some checks need an API enabled or another role as well, for example Organization Policy, Security Command Center and the privilege-escalation check; see The APIs and roles each GCP check needs.
Step 3 - Create and download the key
What to enter in the console¶
Open Settings > Connections > Cloud, add a GCP connection and choose key.json as the Service account key (JSON). The same form opens from Add in the Run wizard's Cloud accounts section. Check tests the key and lists what it can read before anything is saved. See Connections Registry for the form and what follows it.
Keep the key secure
The JSON key is a long-lived credential. Store it in a secrets manager and rotate it at least every 90 days. Delete it from disk after uploading it to the console.
One service account for many projects¶
One service account can scan every project of an organisation (or of a folder). Create it once, in one project, and grant its roles above the projects instead of in each one. Discovery then lists the projects, GKE clusters and Artifact Registry repositories the account can read, and you choose what to scan from that list (see Connections Registry).
Step 1 - Create the service account in a home project
Pick one project to own the service account. It does not have to be a project you scan.
HOME_PROJECT="my-security-project"
SA_NAME="vsutra-ai-inspector"
gcloud iam service-accounts create "$SA_NAME" \
--project="$HOME_PROJECT" \
--display-name="VirtualSutra AI Inspector"
SA_EMAIL="${SA_NAME}@${HOME_PROJECT}.iam.gserviceaccount.com"
Step 2 - Grant the roles at organisation level
A role granted on the organisation applies to every project in it, including projects created later. Grant the five scan roles from the single-project steps above, plus the three roles discovery needs:
| Role | Permission discovery uses | What it is for |
|---|---|---|
roles/browser | resourcemanager.projects.get | Listing the projects the account can read |
roles/container.clusterViewer | container.clusters.list | Listing GKE clusters in each project |
roles/artifactregistry.reader | artifactregistry.repositories.list, artifactregistry.dockerimages.list | Listing Artifact Registry repositories and the images in each Docker repository. The same role lets a scan pull those images |
These four permissions are the ones the connection test checks, and the three roles are the ones the console names when one is missing.
ORG_ID="123456789012" # gcloud organizations list
for ROLE in \
roles/viewer \
roles/iam.securityReviewer \
roles/cloudasset.viewer \
roles/logging.viewer \
roles/monitoring.viewer \
roles/browser \
roles/container.clusterViewer \
roles/artifactregistry.reader; do
gcloud organizations add-iam-policy-binding "$ORG_ID" \
--member="serviceAccount:$SA_EMAIL" \
--role="$ROLE" --condition=None
done
To cover one folder instead of the whole organisation, grant the same roles on the folder:
gcloud resource-manager folders add-iam-policy-binding "$FOLDER_ID" \
--member="serviceAccount:$SA_EMAIL" \
--role="$ROLE" --condition=None
Enter the organisation's numeric ID in the connection's Organisation ID (optional) field. With it, the grant command the console shows for a missing role is the organisation-level one. Without it, the command is per project. The console shows no folder-level command.
Step 3 - Enable the APIs
Discovery calls three Google APIs. A listing whose API is not enabled is reported under Could not list, with the command that enables it.
| API | Where to enable it |
|---|---|
Cloud Resource Manager (cloudresourcemanager.googleapis.com) | The home project, which is the project that owns the service account |
Kubernetes Engine (container.googleapis.com) | Each project whose GKE clusters you want listed |
Artifact Registry (artifactregistry.googleapis.com) | Each project whose repositories you want listed |
gcloud services enable cloudresourcemanager.googleapis.com --project="$HOME_PROJECT"
# In each project that has clusters or repositories to list:
gcloud services enable container.googleapis.com artifactregistry.googleapis.com \
--project="<project-id>"
A project where the Kubernetes Engine or Artifact Registry API is off is still listed as a project. Only its clusters or repositories are missing.
Step 4 - Create the key
Upload key.json when you add the GCP connection. The warning above applies to this key too: it reaches every project the roles cover.
Alternative - grant per project
If you cannot grant at organisation or folder level, keep the single service account and grant the same eight roles in each project you want scanned:
for PROJECT in project-a project-b; do
for ROLE in \
roles/viewer \
roles/iam.securityReviewer \
roles/cloudasset.viewer \
roles/logging.viewer \
roles/monitoring.viewer \
roles/browser \
roles/container.clusterViewer \
roles/artifactregistry.reader; do
gcloud projects add-iam-policy-binding "$PROJECT" \
--member="serviceAccount:$SA_EMAIL" \
--role="$ROLE" --condition=None
done
done
Discovery lists only the projects the account has been granted on. A project created later is not covered until you grant the roles there.
The APIs and roles each GCP check needs¶
The roles and APIs above are what most checks read with. A scan also makes calls that need more: an API enabled in the project, a role the five base roles do not name, or a role granted somewhere other than the project. Without them the check is not evaluated, and the scan says so.
A check that cannot be evaluated leaves a gap in the scan
Every check of a GCP scan runs, whatever the enable_scc, enable_asset_inventory, enable_gke_audit, enable_artifact_registry_scan, enable_cost_analysis and enable_policy_analyzer fields on the connection say: the scan does not read them. Only the Well-Architected pillars and AI workload security (below) are switched by their own fields. The scan's coverage record (below) lists the other five of those groups as run, and the asset-inventory label check (enable_asset_inventory) with its Well-Architected operational-excellence pillar: switched_off while that pillar's own field is off. It lists any Well-Architected pillar or AI workload security group as switched_off when its own field is off, so a switched_off line there is expected.
When a check cannot be evaluated because its API is not enabled or a role is missing, the findings the scan did collect are kept, one Cloud scan coverage record finding per account lists each check that did not run, what it needs and the command that grants it, and the account's task completes with a message of this shape (INCOMPLETE COVERAGE: ...). If no check at all could run, the task fails instead, and so does a scan whose live compliance enrichment gave no usable reply. This holds for AWS, Azure and GCP alike.
INCOMPLETE COVERAGE: gcp account "Production": 3 check(s) could not be evaluated — the task completed and the scan-coverage record says what each needs: permission: GCP_IAM_EFFECTIVE_PRIVILEGE_ESCALATION on 4 scope(s) (needs roles/iam.serviceAccountTokenCreator); service not enabled: GCP_ASSET_NO_ORGPOLICY_vmExternalIpAccess on 1 scope(s) (needs orgpolicy.googleapis.com), GCP_ASSET_NO_ORGPOLICY_requireShieldedVm on 1 scope(s) (needs orgpolicy.googleapis.com)
The account is recorded as a coverage gap and the run ends partial. The compliance controls that depend on the account read "Not assessed - coverage incomplete" (see Compliance). The message names each check once, with how many scopes it affected, what it needs, and carries no commands; it names at most 20 checks (the rest are counted as "and N more") and is cut at 600 characters (ending in …). The commands are in the Cloud scan coverage record finding (title Cloud scan coverage record, check id CKV_VSUTRA_CLOUD_SCAN_COVERAGE, informational): one step per distinct API or role with its command, at most 25 steps, and at most 100 check entries (the rest are counted). Grant what it names, then resume the run or start a new scan. A plain resume runs the cloud scan again for every cloud account of the run, not only the one with the gap.
How a disabled API is read. A check of a service that lists your resources (Compute Engine, Cloud Storage, Kubernetes Engine, Artifact Registry, Cloud SQL and the like) treats a disabled API as "there are none": it finds nothing and records no gap. A check of an audit or policy service (Resource Manager, IAM, Security Command Center, Organization Policy, Cloud Logging, Cloud Billing budgets, Cloud Asset, Cloud Monitoring) cannot be evaluated when its API is off. A permission refusal is a gap.
Commands. These are the commands the product prints, with the same placeholders. Replace them and run them yourself: the product never runs them.
| To | Command |
|---|---|
| Enable an API | gcloud services enable API --project=PROJECT_ID |
| Grant a role on a project | gcloud projects add-iam-policy-binding PROJECT_ID --member=serviceAccount:SERVICE_ACCOUNT_EMAIL --role=ROLE |
| Grant a role on the organisation | gcloud organizations add-iam-policy-binding ORGANIZATION_ID --member=serviceAccount:SERVICE_ACCOUNT_EMAIL --role=ROLE |
| Grant a role on a billing account | gcloud billing accounts add-iam-policy-binding BILLING_ACCOUNT_ID --member=serviceAccount:SERVICE_ACCOUNT_EMAIL --role=ROLE |
| Grant a role on a service account | gcloud iam service-accounts add-iam-policy-binding TARGET_SERVICE_ACCOUNT_EMAIL --member=serviceAccount:SERVICE_ACCOUNT_EMAIL --role=ROLE --project=PROJECT_ID |
SERVICE_ACCOUNT_EMAIL is the account whose key the connection holds. A role granted on the organisation applies to every project in it.
Checks that run on every scan¶
| Check group | What it looks at | API to enable | Role to grant | Granted on |
|---|---|---|---|---|
| IAM policy | Public bindings, primitive roles, admin roles, Owner on service accounts, external accounts in bindings | cloudresourcemanager.googleapis.com | roles/iam.securityReviewer | Project |
| IAM service-account keys | Keys older than 90 days | iam.googleapis.com | roles/iam.securityReviewer | Project |
| Effective permissions: listing service accounts | Which service accounts the project has | iam.googleapis.com | roles/iam.securityReviewer | Project |
| Effective permissions: impersonation | What each service account can do, asked as that account | iamcredentials.googleapis.com, enabled on the project that owns the scanning service account (gcloud services enable iamcredentials.googleapis.com --project=SCANNING_SERVICE_ACCOUNT_PROJECT_ID) | roles/iam.serviceAccountTokenCreator | Each service account being checked |
| Network | Firewall rules open to the internet, default network, subnets without flow logs | compute.googleapis.com | roles/viewer | Project |
| Cloud Storage | Public ACLs, uniform bucket-level access, versioning | storage.googleapis.com | roles/viewer | Project |
| Cloud Storage | Public IAM bindings on a bucket | storage.googleapis.com | roles/iam.securityReviewer | Project |
| Security Command Center | High and critical findings | securitycenter.googleapis.com | roles/securitycenter.findingsViewer | Project, or the organisation when the connection sets an organisation ID |
| Security Command Center | Whether it is enabled (CKV_VSUTRA_GCP_LOG_7) | securitycenter.googleapis.com | roles/securitycenter.sourcesViewer | Project |
| Organization Policy | Five effective constraints: external IPs on VMs, Shielded VMs, service-account key creation, uniform bucket-level access, default network creation | orgpolicy.googleapis.com | roles/orgpolicy.policyViewer | Project |
| Kubernetes Engine | Cluster configuration | container.googleapis.com | roles/container.clusterViewer | Project |
| Artifact Registry | Repository configuration, CMEK | artifactregistry.googleapis.com | roles/artifactregistry.reader | Project |
| Artifact Registry | Public access to a repository | artifactregistry.googleapis.com | roles/iam.securityReviewer | Project |
| Cloud Billing budgets | A budget exists for the billing account. Runs only when the connection has a billing account ID | billingbudgets.googleapis.com | roles/billing.viewer | The billing account |
| Logging: audit logs | Data access audit logs on sensitive services | cloudresourcemanager.googleapis.com | roles/iam.securityReviewer | Project |
| Logging: sinks | A log sink to Cloud Storage or BigQuery | logging.googleapis.com | roles/logging.viewer | Project |
| Logging: flow logs and DNS | VPC flow logs; Cloud DNS logging | compute.googleapis.com; dns.googleapis.com | roles/viewer | Project |
| Backup | Cloud SQL backups and HA; disk snapshots; Cloud Spanner; Filestore; bucket versioning | sqladmin.googleapis.com; compute.googleapis.com; spanner.googleapis.com; file.googleapis.com; storage.googleapis.com | roles/viewer | Project |
| Encryption | CMEK on buckets, BigQuery datasets, Pub/Sub topics, Secret Manager secrets | storage.googleapis.com; bigquery.googleapis.com; pubsub.googleapis.com; secretmanager.googleapis.com | roles/viewer | Project |
Checks you switch on¶
| Check group | Switch | What it looks at | API to enable | Role to grant | Granted on |
|---|---|---|---|---|---|
| AI workload security | enable_ai_spm | Vertex AI endpoints: CMEK, request logging | aiplatform.googleapis.com | roles/viewer | Project |
| AI workload security | enable_ai_spm | Vertex AI endpoints open to everyone | aiplatform.googleapis.com | roles/iam.securityReviewer | Project |
| AI workload security | enable_ai_spm | Endpoint quota limit | serviceusage.googleapis.com | roles/viewer | Project |
| AI workload security | enable_ai_spm | Service accounts of endpoints with excessive permissions | cloudresourcemanager.googleapis.com | roles/iam.securityReviewer | Project |
| Well-Architected: operational excellence | enable_waf_ops_excellence | Resource labelling | cloudasset.googleapis.com | roles/cloudasset.viewer | Project |
| Well-Architected: operational excellence | enable_waf_ops_excellence | Alert policies | monitoring.googleapis.com | roles/monitoring.viewer | Project |
| Well-Architected: operational excellence | enable_waf_ops_excellence | Deployment Manager | deploymentmanager.googleapis.com | roles/viewer | Project |
| Well-Architected: reliability | enable_waf_reliability | Single-zone Cloud SQL; load balancer health checks; instance-group autoscaling | sqladmin.googleapis.com; compute.googleapis.com | roles/viewer | Project |
| Well-Architected: performance | enable_waf_performance | Oversized instances; Memorystore | compute.googleapis.com; redis.googleapis.com | roles/viewer | Project |
| Well-Architected: performance | enable_waf_performance | Instance utilisation metrics | monitoring.googleapis.com | roles/monitoring.viewer | Project |
| Well-Architected: cost | enable_waf_cost | Stopped instances, committed use discounts, bucket lifecycle rules | compute.googleapis.com; storage.googleapis.com | roles/viewer | Project |
| Well-Architected: sustainability | enable_waf_sustainability | Spot ratio, N1 instances | compute.googleapis.com | roles/viewer | Project |
enable_waf_all switches on all five pillars. The sustainability pillar's carbon-footprint check is not supported in this release: it needs nothing from you and is never reported as a gap.
Scanning the GKE clusters discovery lists¶
roles/container.clusterViewer lets discovery list clusters. It reads nothing inside one. To scan a discovered cluster with the account's key, grant roles/container.viewer on the cluster's project (read-only, and it carries every read a cluster scan makes), or bind a read-only ClusterRole inside the cluster. See Access a scan needs.
gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:SERVICE_ACCOUNT_EMAIL --role=roles/container.viewer
Privilege-escalation check: roles/iam.serviceAccountTokenCreator¶
The effective-permission check for GCP asks Google what each service account of the project can do, and the only way to get that answer is to call as that account. The scan therefore mints a short-lived token as each service account, which needs two things:
- the IAM Service Account Credentials API (
iamcredentials.googleapis.com) enabled on the project that owns the scanning service account, which is not always a project you scan; and roles/iam.serviceAccountTokenCreatoron each service account to be checked, granted with the command in the table above.
Without them the check does not run quietly. A service account the scanning account may not impersonate is not evaluated, the others are still checked, the task completes, the run ends partial, and GCP_IAM_EFFECTIVE_PRIVILEGE_ESCALATION is named in the message and in the Cloud scan coverage record, which carries the role and the command that grants it. If the Credentials API is off, no service account of the project is checked and the message names iamcredentials.googleapis.com instead.
This role lets the scanning account act as the service account
Token Creator allows minting tokens as the account it is granted on. The product prints a per-account command and no broader one. Grant it only for service accounts whose permissions you want the scan to judge, and do not grant it at all if you accept the check being reported as not evaluated (the run ends partial and the controls it feeds read Not assessed).
Effective permissions (CIEM): additional permissions¶
CIEM findings run automatically as part of a cloud inspection, but each provider needs one extra permission beyond the base read-only policies above. Without it the check is not evaluated: the findings the scan did collect are kept, the account's task completes with the check named in its message and in the Cloud scan coverage record (which carries the role and the command that grants it), and the run ends partial. This is the same for AWS, Azure and GCP.
| Provider | Additional permission | Notes |
|---|---|---|
| AWS | iam:SimulatePrincipalPolicy | Read-only API; already included in the SecurityAudit managed policy above |
| Azure | Microsoft.Authorization/roleAssignments/read and Microsoft.Authorization/roleDefinitions/read | Included in the built-in Reader role assigned above |
| GCP | iam.serviceAccounts.getAccessToken | Not included in any of the roles granted above. Grant roles/iam.serviceAccountTokenCreator on each service account to be checked, and enable iamcredentials.googleapis.com on the project that owns the scanning service account. See Privilege-escalation check |
AI workload security (AI-SPM): additional permissions¶
AI-SPM scanning reads configuration for AI/ML services in the account. It uses the same base read-only permissions granted above (SecurityAudit + ReadOnlyAccess for AWS, Reader for Azure, roles/viewer for GCP); no additional IAM grants are required for AWS and Azure beyond what's already listed. For GCP, enable aiplatform.googleapis.com and serviceusage.googleapis.com; the roles are the base ones (see the table). Enable it per-account with the enable_ai_spm field on the cloud account connection.
Multiple accounts¶
You can add multiple cloud accounts in a single run - for example, all your AWS accounts across regions, or a mix of AWS and Azure. Each account is scanned independently and findings are tagged with their source account.
Container Registry Access¶
If you want to scan private container images, grant the VirtualSutra scanner read access to your registry. Public images (Docker Hub, GHCR public repos) require no credentials.
AWS ECR¶
Add the AmazonEC2ContainerRegistryReadOnly managed policy to the VirtualSutra inspection role, or add this inline policy statement for least-privilege access:
# Attach the AWS managed read-only ECR policy
aws iam attach-role-policy \
--role-name VirtualSutraInspection \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
The scanner uses the same IAM role as the cloud inspector - no separate credentials needed for ECR images in the same account. For an image in another account of your Organization the scanner assumes that account's member role (see Scanning an AWS Organization), which carries the three actions a pull needs: ecr:GetAuthorizationToken, ecr:BatchGetImage and ecr:GetDownloadUrlForLayer. A scan that is refused names all three.
Azure ACR¶
Grant the service principal the AcrPull role on the container registry:
ACR_ID=$(az acr show --name <acr-name> --resource-group <rg> --query id -o tsv)
az role assignment create \
--assignee "<client-id>" \
--role "AcrPull" \
--scope "$ACR_ID"
GCP Artifact Registry¶
Grant the service account roles/artifactregistry.reader on the project:
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:vsutra-ai-inspector@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/artifactregistry.reader"
Private registries (Docker Hub, GHCR, self-hosted)¶
For other private registries, provide credentials directly in the container image config:
| Config field | Description |
|---|---|
credentials_ref | Secret reference for the registry password (e.g. env://REGISTRY_PASSWORD) |
username_ref | Secret reference for the registry username (e.g. env://REGISTRY_USERNAME) |
Using the setup script
The setup_cloud_resources.sh script automates IAM role/policy setup for ECR, ACR, and Artifact Registry:
What the setup script does for GCP
setup_cloud_resources.sh gcp --project-id PROJECT_ID creates the service account and a key and grants a set of project roles (the five base roles, roles/container.clusterViewer, roles/container.viewer, roles/billing.viewer, roles/securitycenter.findingsViewer, roles/artifactregistry.reader and three roles for the Well-Architected pillars). With --org-id it prints, and does not run, the organisation-level grants. It does not enable any API, does not grant roles/orgpolicy.policyViewer on the project, roles/securitycenter.sourcesViewer on the project, roles/browser or roles/iam.serviceAccountTokenCreator, and grants roles/billing.viewer on the project, where a budget check needs it on the billing account. Run the commands in The APIs and roles each GCP check needs for what it leaves out.
Next step
Once your cloud accounts are configured, go back to Running an Inspection and add them to the wizard.