> For the complete documentation index, see [llms.txt](https://cortex-docs.paloaltonetworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cortex-docs.paloaltonetworks.com/google-cloud-platform-manual-onboarding/gcp-manual-onboarding/gcp-manual-onboarding-guide-project-scope/phase-2-platform-service-accounts.md).

# Phase 2: Platform service accounts

This phase creates the main service account used by Cortex to monitor your GCP resources.

Note: All GCP resources that support labels must include the following labels:

* `managed_by=paloaltonetworks`
* Any additional labels you configured in the GCP onboarding wizard in Cortex.

To add a label to a GCP resource, use the label flag:

```shell
--labels managed_by=paloaltonetworks,<your_key>=<your_value>
```

### 2.1 Create the Platform Service Account (Required)

Create a service account that Cortex will use as the primary identity for accessing your resources.

```shell
gcloud iam service-accounts create <PLATFORM_SA_NAME> \
    --display-name="Cortex Platform Service Account"
```

Where `<PLATFORM_SA_NAME>` is your chosen service account ID (e.g., cortex-platform).

### 2.2 Grant predefined managed roles to the Platform Service Account (Required)

Module: Assets Discovery

Bind the following predefined roles to the platform service account:

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="roles/cloudfunctions.viewer"

gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="roles/container.clusterViewer"

gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="roles/storage.objectViewer"

gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="roles/firebaserules.viewer"

gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="roles/viewer"
```

Where:

* `<PLATFORM_SA_NAME>` is the platform service account name
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

After running the last command, the following prompt is displayed:

```
[1] EXPRESSION=((resource.name.extract("snapshots/{end}").startsWith("cortex-scan-") || resource.name.extract("disks/{end}").startsWith("cortex-scan-")) && resource.service == "compute.googleapis.com"), TITLE=scoped-permission-condition
[2] None
[3] Specify a new condition
The policy contains bindings with conditions, so specifying a condition is required when adding a binding. Please specify a condition.:
```

Choose "None" by entering 2.

### 2.3 Create the Custom Role and grant it to the Platform Service Account (Required)

Module: Assets Discovery

Create a custom role with granular permissions not covered by the built-in roles.

#### 2.3.1 Create the Custom Role

{% code overflow="wrap" %}

```shell
gcloud iam roles create <CLOUD_VIEWER_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="Cortex Platform Cloud Viewer" \
    --description="Custom role for Cortex Cloud. Contains granular additional permissions not covered by built-in roles." \
    --stage=GA \
    --permissions="apigateway.apis.list,apigateway.locations.get,backupdr.backupPlanAssociations.list,backupdr.backupPlans.list,backupdr.backupVaults.list,bigquery.tables.get,bigquery.dataPolicies.list,bigquery.bireservations.get,bigquery.reservations.list,clientauthconfig.clients.listWithSecrets,resourcemanager.tagKeys.list,cloudsecurityscanner.scans.list,cloudtasks.queues.list,cloudtasks.tasks.list,compute.reservations.getIamPolicy,connectors.endpointAttachments.list,connectors.customConnectors.list,connectors.managedZones.list,connectors.locations.list,connectors.providers.list,datacatalog.catalogs.searchAll,dataflow.jobs.list,datapipelines.pipelines.list,dataproc.batches.list,dataproc.sessionTemplates.list,dataproc.sessions.list,deploymentmanager.deployments.getIamPolicy,firebaserules.rulesets.get,iam.workloadIdentityPoolProviders.list,iam.workloadIdentityPools.list,iap.projects.getSettings,logging.cmekSettings.get,looker.instances.list,networkservices.meshes.getIamPolicy,osconfig.projectFeatureSettings.get,osconfig.patchDeployments.list,bigtable.appProfiles.list,bigtable.clusters.list,bigtable.backups.list,bigtable.backups.getIamPolicy,bigtable.instances.get,bigtable.instances.getIamPolicy,bigtable.instances.list,pubsub.subscriptions.getIamPolicy,pubsub.topics.getIamPolicy,redis.clusters.list,resourcemanager.projects.get,serviceusage.services.use,servicemanagement.services.bind,servicemanagement.services.getIamPolicy,storage.buckets.get,storage.buckets.getIamPolicy,storage.objects.getIamPolicy,tpu.nodes.list,cloudasset.assets.searchAllIamPolicies,run.services.get,run.services.getIamPolicy,run.services.list"
```

{% endcode %}

Where `<CLOUD_VIEWER_ROLE_ID>` is your chosen role ID (e.g., CortexCloudViewer).

#### 2.3.2 Grant the Custom Role to the Platform Service Account

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<CLOUD_VIEWER_ROLE_ID>"
```

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<CLOUD_VIEWER_ROLE_ID>` is your chosen role ID (e.g., CortexCloudViewer).

### 2.4 Grant the Service Account Token Creator role to the Outpost Service Account (Required)

Allow the Outpost Service Account (provided by Cortex) to impersonate the Platform Service Account:

```shell
gcloud iam service-accounts add-iam-policy-binding \
    <PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com \
    --member="serviceAccount:<OUTPOST_SERVICE_ACCOUNT_EMAIL>" \
    --role="roles/iam.serviceAccountTokenCreator"
```

Where:

* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<OUTPOST_SERVICE_ACCOUNT_EMAIL>` is the value listed in the identifiers JSON file

### 2.5 Agentless Disk Scanning (ADS) (Optional)

Module: ADS

Execute the steps in this section if you enabled Agentless Disk Scanning in the GCP onboarding wizard in Cortex.

#### 2.5.1 Create the ADS Connector Role

```shell
gcloud iam roles create <ADS_CONNECTOR_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="ADS Connector Role" \
    --permissions="\
compute.snapshots.get,\
compute.snapshots.create,\
compute.snapshots.delete,\
compute.snapshots.setLabels,\
compute.disks.create,\
compute.disks.get,\
compute.disks.delete,\
compute.disks.setLabels,\
compute.images.get"
```

Where:

* `<ADS_CONNECTOR_ROLE_ID>` is your chosen role ID (e.g., CortexAdsConnector).
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.5.2 Grant the ADS Connector role to the Platform Service Account

Grant the ADS Connector role to the Platform Service Account and apply an IAM condition to restrict permissions to resources prefixed with `cortex-scan-*`:

{% code overflow="wrap" %}

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<ADS_CONNECTOR_ROLE_ID>" \
    --condition='title=scoped-permission-condition,expression=((resource.name.extract("snapshots/{end}").startsWith("cortex-scan-") || resource.name.extract("disks/{end}").startsWith("cortex-scan-")) && resource.service == "compute.googleapis.com")'
```

{% endcode %}

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<ADS_CONNECTOR_ROLE_ID>` is your chosen role ID (e.g., CortexAdsConnector).

#### 2.5.3 Create the ADS Scanned Assets role

```shellscript
gcloud iam roles create <ADS_SCANNED_ASSETS_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="ADS Scanned Assets Role" \
    --permissions="compute.disks.createSnapshot"
```

Where:

* `<ADS_SCANNED_ASSETS_ROLE_ID>` is your chosen role ID (e.g., CortexAdsScannedAssets).
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.5.4 Grant the ADS Scanned Assets role to the Platform Service Account

Grant the ADS Scanned Assets role to the Platform Service Account. Unlike the ADS Connector role, this role is granted without an IAM condition.

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<ADS_SCANNED_ASSETS_ROLE_ID>" \
    --condition=None
```

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<ADS_SCANNED_ASSETS_ROLE_ID>` is your chosen role ID (e.g., CortexAdsScannedAssets).

#### 2.5.5 Create the ADS Outpost role

```shell
gcloud iam roles create <ADS_OUTPOST_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="ADS Outpost Role" \
    --permissions="compute.snapshots.useReadOnly"
```

Where:

* `<ADS_OUTPOST_ROLE_ID>` is your chosen role ID (e.g., CortexAdsOutpost)
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.5.6 Grant the ADS Outpost role to the Outpost Service Account

Grant the ADS Outpost role to the Outpost Service Account and apply an IAM condition to restrict permissions to resources prefixed with `cortex-scan-*`:

{% code overflow="wrap" %}

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<OUTPOST_SERVICE_ACCOUNT_EMAIL>" \
    --role="projects/<PROJECT_ID>/roles/<ADS_OUTPOST_ROLE_ID>" \
    --condition='title=scoped-permission-condition,expression=((resource.name.extract("snapshots/{end}").startsWith("cortex-scan-") || resource.name.extract("disks/{end}").startsWith("cortex-scan-")) && resource.service == "compute.googleapis.com")'
```

{% endcode %}

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<OUTPOST_SERVICE_ACCOUNT_EMAIL>` is the value listed in the identifiers JSON file
* `<ADS_OUTPOST_ROLE_ID>` is your chosen role ID (e.g., CortexAdsOutpost)

### 2.6 Automation (Optional)

Module: AUTOMATION

Execute the steps in this section if you enabled Automation in the GCP onboarding wizard in Cortex.

#### 2.6.1 Create the Automation role

{% code overflow="wrap" %}

```shell
gcloud iam roles create <AUTOMATION_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="Automation Role" \
    --permissions="\
cloudasset.assets.searchAllResources,\
compute.firewalls.create,compute.firewalls.get,compute.firewalls.list,compute.firewalls.update,\
compute.images.get,compute.instanceGroups.get,\
compute.instances.get,compute.instances.list,compute.instances.setLabels,\
compute.instances.setMetadata,compute.instances.setServiceAccount,\
compute.instances.setTags,compute.instances.start,compute.instances.stop,\
compute.networks.create,compute.networks.get,compute.networks.list,compute.networks.updatePolicy,\
compute.regions.get,compute.snapshots.get,compute.snapshots.list,\
compute.subnetworks.get,compute.subnetworks.list,\
compute.subnetworks.setPrivateIpGoogleAccess,compute.subnetworks.update,compute.zones.get,\
container.clusters.get,container.clusters.list,container.clusters.update,\
resourcemanager.projects.getIamPolicy,resourcemanager.projects.setIamPolicy,\
storage.buckets.get,storage.buckets.getIamPolicy,storage.buckets.getIpFilter,\
storage.buckets.list,storage.buckets.setIamPolicy,storage.buckets.update,\
storage.objects.getIamPolicy,storage.objects.list,\
bigquery.datasets.get,bigquery.datasets.getIamPolicy,\
bigquery.datasets.update,bigquery.datasets.setIamPolicy"
```

{% endcode %}

Where:

* `<AUTOMATION_ROLE_ID>` is your chosen name for the role (e.g. CortexAutomation)
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.6.2 Grant the Automation role to the Platform Service Account

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<AUTOMATION_ROLE_ID>"
```

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<AUTOMATION_ROLE_ID>` is your chosen name for the role (e.g. CortexAutomation)

### 2.7 DSPM: Platform Service Account (Optional)

Module: DSPM

Execute the steps in this section if you enabled Data Security Posture Management (DSPM) in the GCP onboarding wizard in Cortex.

#### 2.7.1 Create the DSPM Connector role

{% code overflow="wrap" %}

```shell
gcloud iam roles create <DSPM_CONNECTOR_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="DSPM Connector Role" \
    --permissions="\
cloudsql.backupRuns.create,cloudsql.backupRuns.delete,cloudsql.backupRuns.get,\
cloudsql.backupRuns.list,bigtable.backups.create,bigtable.backups.delete,\
bigtable.backups.get,bigtable.backups.list,bigtable.clusters.get,\
bigtable.clusters.list,bigtable.instances.get,bigtable.instances.list,\
bigtable.tables.get,bigtable.tables.list,bigtable.tables.readRows,\
bigquery.tables.get,bigquery.tables.list"
```

{% endcode %}

Where:

* `<DSPM_CONNECTOR_ROLE_ID>` is your chosen name for the role (e.g. CortexDspmConnector)
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.7.2 Grant the DSPM Connector role to the Platform Service Account

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<DSPM_CONNECTOR_ROLE_ID>"
```

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<DSPM_CONNECTOR_ROLE_ID>` is your chosen name for the role (e.g. CortexDspmConnector)

#### 2.8 Kubernetes Security (Optional)

Module: K8S\_SECURITY

Execute the steps in this section if you enabled Kubernetes Security in the GCP onboarding wizard in Cortex.

#### 2.8.1 Create the Kubernetes Security role

```shell
gcloud iam roles create <KUBERNETES_SECURITY_ROLE_ID> \
    --project=<PROJECT_ID> \
    --title="Kubernetes Security Role" \
    --permissions="container.secrets.list"
```

Where:

* `<KUBERNETES_SECURITY_ROLE_ID>` is your chosen name for the role (e.g. CortexKubernetesSecurity)
* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex

#### 2.8.2 Grant the Kubernetes Security role to the Platform Service Account

```shell
gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:<PLATFORM_SA_NAME>@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="projects/<PROJECT_ID>/roles/<KUBERNETES_SECURITY_ROLE_ID>"
```

Where:

* `<PROJECT_ID>` is the ID of the GCP project you are onboarding to Cortex
* `<PLATFORM_SA_NAME>` is the Platform Service Account name
* `<KUBERNETES_SECURITY_ROLE_ID>` is your chosen name for the role (e.g. CortexKubernetesSecurity)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://cortex-docs.paloaltonetworks.com/google-cloud-platform-manual-onboarding/gcp-manual-onboarding/gcp-manual-onboarding-guide-project-scope/phase-2-platform-service-accounts.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
